Repository: oppia/oppia

Issue:
Update docstrings in the Python backend code to follow the style guide.
We've been a bit lax about adding docstrings to code in the backend. Instead, we should be following the style guide: https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments -- docstrings should define args, return values, exceptions, etc. The aim of this issue is to ensure that the backend code is properly documented; we will tackle this on a file-by-file basis.

Note: if you're a new contributor, this issue is a particularly good introductory one to tackle, since it will require reading, understanding and documenting the purpose of a file in the codebase, and help you get acquainted with how the Oppia backend is structured. It's important that you're able to communicate and write clearly, though!

Some useful tips:
-  Please follow our [style guide](https://github.com/oppia/oppia/wiki/Coding-style-guide#general) (and also the [Google Python style guide](https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments)) when writing docstrings. Make sure to follow the patterns established there as closely as possible, including spacing, capitalization and punctuation. Also, it's a good idea to look at some of the existing files (see below) to get a sense of the correct format -- we recommend following this closely in order to maintain uniformity across the codebase.
- If applicable, make sure that the docstrings for all functions correctly specify the args, return values, etc. If a function has no args, you can omit the Args section; if it has no return value, you can omit the Returns section.
- If a docstring is not present in the code, or the existing one is too vague for you to understand what's going on just by looking at it, then it should be rewritten to be more clear. You'll often need to read the code as well, and do a grep on the callsites to see how it's being used, in order to get a clear understanding of what it's doing. This is good practice for learning how to navigate the codebase, and is an intended part of this project.
- If you feel that parts of the codebase are unclear or incorrectly factored, please feel free to bring this up! The whole point of docstrings is to improve code health and accessibility for new developers, so if you spot other things that can be done in service of that aim, we should also look into doing that.

**Completed:**
- [x] core.controllers.base @shaz13 
- [x] core.controllers.cron @saeedjassani
- [x] core.controllers.reader @96mohitm
- [x] core.domain.activity_domain @andromfins
- [x] core.domain.activity_services @souravsingh
- [x] core.domain.classifier_services @andromfins 
- [x] core.domain.collection_domain @domevit
- [x] core.domain.collection_services @kahkhang 
- [x] core.domain.config_services @vibhor98 
- [x] core.domain.email_manager @Oishikatta 
- [x] core.domain.exp_domain @domevit
- [x] core.domain.exp_services @DubeySandeep
- [x] core.domain.feedback_domain @terrameijar 
- [x] core.domain.feedback_jobs_continuous
- [x] core.domain.feedback_services @andimeo
- [x] core.domain.fs_domain @kartik-25 
- [x] core.domain.html_cleaner @seanlip
- [x] core.domain.param_domain @rohan-gulati
- [x] core.domain.rating_services @BojOnTheBeat 
- [x] core.domain.rights_manager @ayejay
- [x] core.domain.stats_domain @Arunabh98
- [x] core.domain.stats_jobs_continuous @Arunabh98 
- [x] core.domain.stats_jobs_one_off @zpuller
- [x] core.domain.stats_services @manoj-makkuboy 
- [x] core.domain.subscription_services @vibhor98 
- [x] core.domain.summary_services @domevit 
- [x] core.domain.user_jobs_continuous @shubha1593 
- [x] core.domain.user_services @Nalinc 
- [x] core.domain.value_generators_domain @meetsha 
- [x] core.jobs @brianrodri
- [x] core.platform.app_identity.gae_app_identity_services @ubhisat
- [x] core.platform.models @pranavsid98 
- [x] core.platform.transactions.gae_transaction_services @ubhisat
- [x] core.storage.base_model.gae_models @vojtechjelinek
- [x] core.storage.collection.gae_models @kaggis
- [x] core.storage.email.gae_models @rajan-garg
- [x] core.storage.exploration.gae_models @kirankonduru
- [x] core.storage.feedback.gae_models @naveenshukla
- [x] core.storage.file.gae_models @Ian91 
- [x] core.storage.job.gae_models @sasacocic
- [x] core.storage.statistics.gae_models @abhishekarya286
- [x] core.storage.user.gae_models @Anthony-Alridge 
- [x] core.tests.test_util_jobs @seanlip
- [x] core.tests.test_utils @vibhor98
- [x] utils @yashLadha 


Discussion:
@seanlip I would like to work in the issue.

Hi @souravsingh, do you think you could finish up #2051 first, please? Thanks! 

@seanlip I would like to work on this issue.

Hi @andromfins -- now that you've had a go at writing docstrings for a single class, would you mind picking one or two other files and doing the same thing for those? Let me know which ones you pick, so that I can mark you off against them.

Thanks!

Hi, i'm working on **core.domain.classifier_services**.

OK, thanks @andromfins! 

@seanlip Can I take a stab at other file to add docstrings?

Yep, sounds good. Could you take core.domain.html_cleaner? Thanks!

@andromfins -- are you still working on this?

  @seanlip Yes!

I would like to work on `core.domain.stats_domain`

@Arunabh98 assigned you on `core.domain.stats_domain`

Almost completed `core.domain.stats_domain`. Now working on `core.domain.stats_jobs_continuous`

hi @seanlip 
I am working on this issue.
I am writing the docstrings for /core/storage/collection/gae_models.py as i was assigned as a starting project.

@seanlip can I work on core.controllers.cron?
Yep, sounds good! I've updated the list.
@seanlip I'd like to work on core.storage.job.gae_models.
Sounds good, thanks @sasacocic! I've updated the list. 
@seanlip I'd like to work on core.platform.models. Thanks

@seanlip I'd like to work on core.domain.stats_services
Sounds good, thanks @arjunmahishi! I've assigned you. 
@seanlip I'd like to work on core.domain.summary_services
Sounds good, thanks! I've assigned you.
@seanlip Hello, i would work on core.domain.config_domain if possible.
@tvallois I have assigned you to that. Thanks
@seanlip I can do core.domain.rights_manager.py
Sounds good @fodra, I've assigned you. Thanks! 
@seanlip I would like to take _core.tests.test_util_jobs_
Sounds great, I've assigned you. Thanks @terrameijar! 
@seanlip could i work on  core.storage.statistics.gae_models?

I'd like to contribute to this, could you please assign me one of the sub tasks?
Hi @fsam95, welcome to Oppia! Could you please follow the instructions [here](https://github.com/oppia/oppia/wiki/Contributing-code-to-Oppia#setting-things-up) first?

@cosmicAsymmetry The list of tasks that are yet to be done is in the "Not started" section. Would you be up for taking a look at the corresponding files, and picking one that looks approachable? Note that test_utils has some prior work that you might be able to extend, though it's slightly unusual because the functions within it are meant mainly for supporting backend testing (and are therefore not directly user-facing). Thanks!
I've followed the instructions :). 
Thanks @fsam95! Assigned you. 
@seanlip I want to take one of the files but it looks like all the files in "Not started" section have been documented? Can you assign me to one which has not been taken? Thanks! :)
Hi @zhengxit -- actually, all those files still need work, I think, be it improving the existing docstrings, writing new docstrings for methods/functions which don't have them, or making sure that everything follows the correct style. So, please feel free to pick one of them!
(P.S. If you see a particular file in the "Not started" section that you're not sure what to do with, just ask.)
@seanlip I would like to take **core.controllers.base**

Assigned. Thanks @shaz13! 
I'm starting work on **core.jobs**.

Resolving patch:
diff --git a/core/storage/file/gae_models.py b/core/storage/file/gae_models.py
--- a/core/storage/file/gae_models.py
+++ b/core/storage/file/gae_models.py
@@ -37,7 +37,9 @@ class FileMetadataSnapshotContentModel(base_models.BaseSnapshotContentModel):
 
 class FileMetadataModel(base_models.VersionedModel):
     """File metadata model, keyed by exploration id and absolute file name."""
+    # The class which stores the commit history of the metadata snapshots.
     SNAPSHOT_METADATA_CLASS = FileMetadataSnapshotMetadataModel
+    # The class which stores the content of the metadata snapshots.
     SNAPSHOT_CONTENT_CLASS = FileMetadataSnapshotContentModel
 
     # The size of the file.
@@ -49,31 +51,98 @@ def get_new_id(cls, entity_name):
 
     @classmethod
     def get_undeleted(cls):
+        """Returns a list of undeleted FileMetadataModel instances. At most
+        feconf.DEFAULT_QUERY_LIMIT results are returned.
+
+        Returns:
+            list(FileMetadataModel). Contains all undeleted instances of
+            this class.
+        """
         return cls.get_all().filter(cls.deleted == False).fetch(  # pylint: disable=singleton-comparison
             feconf.DEFAULT_QUERY_LIMIT)
 
     @classmethod
     def _construct_id(cls, exploration_id, filepath):
+        """Constructs and returns an id string uniquely identifying the given
+        exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the
+                exploration.
+
+        Returns:
+            str. Uniquely identifying string for the given exploration and
+            filepath (concatenation of exploration id and filepath).
+        """
         return utils.vfs_construct_path('/', exploration_id, filepath)
 
     @classmethod
     def create(cls, exploration_id, filepath):
+        """Creates and returns a FileMetadataModel instance describing the given
+        exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the exploration.
+
+        Returns:
+            FileMetadataModel. An instance of this class.
+        """
         model_id = cls._construct_id(exploration_id, filepath)
         return cls(id=model_id, deleted=False)
 
     @classmethod
     def get_model(cls, exploration_id, filepath, strict=False):
+        """Returns the newest version of an existing FileMetadataModel instance
+        describing the given exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the exploration.
+
+        Returns:
+            FileMetadataModel. An instance of this class, uniquely
+            identified by exploration_id and filepath.
+        """
         model_id = cls._construct_id(exploration_id, filepath)
         return super(FileMetadataModel, cls).get(model_id, strict=strict)
 
     @classmethod
     def get_version(cls, exploration_id, filepath, version_number):
+        """Returns the specified version of an existing FileMetadataModel
+        instance describing the given exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the exploration.
+            version_number: int. The version number of the instance to
+                be returned.
+
+        Returns:
+            FileMetadataModel. An instance of this class, uniquely identified
+            by exploration_id, filepath, and version_number.
+        """
         model_id = cls._construct_id(exploration_id, filepath)
         return super(FileMetadataModel, cls).get_version(
             model_id, version_number)
 
     def commit(self, committer_id, commit_cmds):
-        return super(FileMetadataModel, self).commit(
+        """Saves a version snapshot of a FileMetadataModel instance and updates
+        the instance.
+
+        Args:
+            committer_id: str. The user_id of the user who committed the change.
+            commit_cmds: list(dict). A list of commands describing changes made
+                in this instance. Must give sufficient information to
+                reconstruct the commit. Dict always contains:
+                    cmd: str. Unique command.
+                followed by additional arguments for that command. For example:
+
+                {'cmd': 'AUTO_revert_version_number'
+                 'version_number': 4}
+        """
+        super(FileMetadataModel, self).commit(
             committer_id, '', commit_cmds)
 
 
@@ -83,7 +152,9 @@ class FileSnapshotMetadataModel(base_models.BaseSnapshotMetadataModel):
 
 
 class FileSnapshotContentModel(base_models.BaseSnapshotContentModel):
-    """Class for storing the content of the file snapshots."""
+    """Class for storing the content of the file snapshots (where the content
+    is an uninterpreted bytestring).
+    """
 
     # Overwrite the superclass member to use a BlobProperty for raw strings.
     content = ndb.BlobProperty(indexed=False)
@@ -91,19 +162,37 @@ class FileSnapshotContentModel(base_models.BaseSnapshotContentModel):
 
 class FileModel(base_models.VersionedModel):
     """File data model, keyed by exploration id and absolute file name."""
+
+    # The class which stores the commit history of the file snapshots.
     SNAPSHOT_METADATA_CLASS = FileSnapshotMetadataModel
+    # The class which stores the content of the file snapshots.
     SNAPSHOT_CONTENT_CLASS = FileSnapshotContentModel
 
     # The contents of the file.
     content = ndb.BlobProperty(indexed=False)
 
     def _reconstitute(self, snapshot_blob):
-        """Manually overwrite the superclass method."""
+        """Overrides the superclass method. Reconstitutes a FileModel
+        instance from a snapshot.
+
+        Args:
+            snapshot_blob: str. A bytestring to which this instance's content
+                field will be set.
+
+        Returns:
+            FileModel. An instance of this class, with content set to
+            snapshot_blob.
+        """
         self.content = snapshot_blob
         return self
 
     def _compute_snapshot(self):
-        """Manually overwrite the superclass method."""
+        """Manually overwrite the superclass method. Returns the content of
+        this FileModel instance.
+
+        Returns:
+            str. A bytestring snapshot of this instance's content.
+        """
         return self.content
 
     @classmethod
@@ -112,22 +201,81 @@ def get_new_id(cls, entity_name):
 
     @classmethod
     def _construct_id(cls, exploration_id, filepath):
+        """Constructs and returns an id string uniquely identifying the given
+        exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the exploration.
+
+        Returns:
+            str. Uniquely identifying string for the given exploration and
+            filepath.
+        """
         return utils.vfs_construct_path('/', exploration_id, filepath)
 
     @classmethod
     def create(cls, exploration_id, filepath):
+        """Creates and returns a FileModel instance specified by the given
+        exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the exploration.
+
+        Returns:
+            FileModel. An instance of this class.
+        """
         model_id = cls._construct_id(exploration_id, filepath)
         return cls(id=model_id, deleted=False)
 
     @classmethod
     def get_model(cls, exploration_id, filepath, strict=False):
+        """Returns the newest version of an existing FileModel instance
+        specified by the given exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the exploration.
+
+        Returns:
+            FileModel. An instance of this class, uniquely
+            identified by exploration_id and filepath.
+        """
         model_id = cls._construct_id(exploration_id, filepath)
         return super(FileModel, cls).get(model_id, strict=strict)
 
     def commit(self, committer_id, commit_cmds):
-        return super(FileModel, self).commit(committer_id, '', commit_cmds)
+        """Saves a version snapshot of a FileModel instance and updates the
+        instance.
+
+        Args:
+            committer_id: str. The user_id of the user who committed the change.
+            commit_cmds: list(dict). A list of commands describing changes made
+                in this instance. Must give sufficient information to
+                reconstruct the commit. Dict always contains:
+                    cmd: str. Unique command.
+                followed by additional arguments for that command. For example:
+
+                {'cmd': 'AUTO_revert_version_number'
+                 'version_number': 4}
+        """
+        super(FileModel, self).commit(committer_id, '', commit_cmds)
 
     @classmethod
     def get_version(cls, exploration_id, filepath, version_number):
+        """Returns the chosen version of an existing FileModel instance
+        specified by the given exploration and filepath.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            filepath: str. The path to the relevant file within the exploration.
+            version_number: int. The version number of the instance to
+                be returned.
+
+        Returns:
+            FileModel. An instance of this class, uniquely
+                identified by exploration_id, filepath, and version_number.
+        """
         model_id = cls._construct_id(exploration_id, filepath)
         return super(FileModel, cls).get_version(model_id, version_number)


Test patch: