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.


Resolving patch:
diff --git a/core/storage/exploration/gae_models.py b/core/storage/exploration/gae_models.py
--- a/core/storage/exploration/gae_models.py
+++ b/core/storage/exploration/gae_models.py
@@ -96,7 +96,18 @@ def get_exploration_count(cls):
         return cls.get_all().count()
 
     def commit(self, committer_id, commit_message, commit_cmds):
-        """Updates the exploration using the properties dict, then saves it."""
+        """Updates the exploration using the properties dict, then saves it.
+
+        Args:
+            committer_id: str. The user id of the user who committed the
+                change.
+            commit_message: str. The commit description message.
+            commit_cmds: list(dict). A list of commands, describing changes
+                made in this model, which should give sufficient information to
+                reconstruct the commit. Each dict always contains:
+                    cmd: str. Unique command.
+                and additional arguments for that command.
+        """
         super(ExplorationModel, self).commit(
             committer_id, commit_message, commit_cmds)
 
@@ -105,6 +116,18 @@ def _trusted_commit(
         """Record the event to the commit log after the model commit.
 
         Note that this extends the superclass method.
+
+        Args:
+            committer_id: str. The user_id of the user who committed the
+                change.
+            commit_type: str. The type of commit. Possible values are in
+                core.storage.base_models.COMMIT_TYPE_CHOICES.
+            commit_message: str. The commit description message.
+            commit_cmds: list(dict). A list of commands, describing changes
+                made in this model, which should give sufficient information to
+                reconstruct the commit. Each dict always contains:
+                    cmd: str. Unique command.
+                and then additional arguments for that command.
         """
         super(ExplorationModel, self)._trusted_commit(
             committer_id, commit_type, commit_message, commit_cmds)
@@ -195,7 +218,19 @@ def _trusted_commit(
             self, committer_id, commit_type, commit_message, commit_cmds):
         """Record the event to the commit log after the model commit.
 
-        Note that this overrides the superclass method.
+        Note that this extends the superclass method.
+
+        Args:
+            committer_id: str. The user_id of the user who committed the
+                change.
+            commit_type: str. The type of commit. Possible values are in
+                core.storage.base_models.COMMIT_TYPE_CHOICES.
+            commit_message: str. The commit description message.
+            commit_cmds: list(dict). A list of commands, describing changes
+                made in this model, should give sufficient information to
+                reconstruct the commit. Each dict always contains:
+                    cmd: str. Unique command.
+                and then additional arguments for that command.
         """
         super(ExplorationRightsModel, self)._trusted_commit(
             committer_id, commit_type, commit_message, commit_cmds)
@@ -269,12 +304,58 @@ class ExplorationCommitLogEntryModel(base_models.BaseModel):
 
     @classmethod
     def get_all_commits(cls, page_size, urlsafe_start_cursor):
+        """Fetches a list of all the commits sorted by their last updated
+        attribute.
+
+        Args:
+            page_size: int. The maximum number of entities to be returned.
+            urlsafe_start_cursor: str or None. If provided, the list of
+                returned entities starts from this datastore cursor.
+                Otherwise, the returned entities start from the beginning
+                of the full list of entities.
+
+        Returns:
+            3-tuple of (results, cursor, more) as described in fetch_page() at:
+            https://developers.google.com/appengine/docs/python/ndb/queryclass,
+            where:
+                results: List of query results.
+                cursor: str or None. A query cursor pointing to the next
+                    batch of results. If there are no more results, this will
+                    be None.
+                more: bool. If True, there are (probably) more results after
+                    this batch. If False, there are no further results after
+                    this batch.
+        """
         return cls._fetch_page_sorted_by_last_updated(
             cls.query(), page_size, urlsafe_start_cursor)
 
     @classmethod
     def get_all_non_private_commits(
             cls, page_size, urlsafe_start_cursor, max_age=None):
+        """Fetches a list of all the non-private commits sorted by their
+        last updated attribute.
+
+        Args:
+            page_size: int. The maximum number of entities to be returned.
+            urlsafe_start_cursor: str or None. If provided, the list of
+                returned entities starts from this datastore cursor.
+                Otherwise, the returned entities start from the beginning
+                of the full list of entities.
+            max_age: datetime.timedelta. The maximum time duration within which
+                commits are needed.
+
+        Returns:
+            3-tuple of (results, cursor, more) which were created which were
+            created no earlier than max_age before the current time where:
+                results: List of query results.
+                cursor: str or None. A query cursor pointing to the next
+                    batch of results. If there are no more results, this will
+                    be None.
+                more: bool. If True, there are (probably) more results after
+                    this batch. If False, there are no further results after
+                    this batch.
+        """
+
         if not isinstance(max_age, datetime.timedelta) and max_age is not None:
             raise ValueError(
                 'max_age must be a datetime.timedelta instance or None.')
@@ -363,7 +444,11 @@ class ExpSummaryModel(base_models.BaseModel):
 
     @classmethod
     def get_non_private(cls):
-        """Returns an iterable with non-private exp summary models."""
+        """Returns an iterable with non-private ExpSummary models.
+
+        Returns:
+            iterable. An iterable with non-private ExpSummary models.
+        """
         return ExpSummaryModel.query().filter(
             ExpSummaryModel.status != feconf.ACTIVITY_STATUS_PRIVATE
         ).filter(
@@ -372,8 +457,15 @@ def get_non_private(cls):
 
     @classmethod
     def get_top_rated(cls, limit):
-        """Returns an iterable with the top rated exp summaries that are
-        public in descending order.
+        """Fetches the top-rated exp summaries that are public in descending
+        order of scaled_average_rating.
+
+        Args:
+            limit: int. The maximum number of results to return.
+
+        Returns:
+            iterable. An iterable with the top rated exp summaries that are
+                public in descending order of scaled_average_rating.
         """
         return ExpSummaryModel.query().filter(
             ndb.OR(ExpSummaryModel.status == feconf.ACTIVITY_STATUS_PUBLIC,
@@ -386,8 +478,15 @@ def get_top_rated(cls, limit):
 
     @classmethod
     def get_private_at_least_viewable(cls, user_id):
-        """Returns an iterable with private exp summaries that are at least
-        viewable by the given user.
+        """Fetches private exp summaries that are at least viewable by the
+        given user.
+
+        Args:
+            user_id: The id of the given user.
+
+        Returns:
+            iterable. An iterable with private exp summaries that are at least
+                viewable by the given user.
         """
         return ExpSummaryModel.query().filter(
             ExpSummaryModel.status == feconf.ACTIVITY_STATUS_PRIVATE
@@ -401,8 +500,14 @@ def get_private_at_least_viewable(cls, user_id):
 
     @classmethod
     def get_at_least_editable(cls, user_id):
-        """Returns an iterable with exp summaries that are at least
-        editable by the given user.
+        """Fetches exp summaries that are at least editable by the given user.
+
+        Args:
+            user_id: The id of the given user.
+
+        Returns:
+            iterable. An iterable with exp summaries that are at least
+                editable by the given user.
         """
         return ExpSummaryModel.query().filter(
             ndb.OR(ExpSummaryModel.owner_ids == user_id,
@@ -413,8 +518,15 @@ def get_at_least_editable(cls, user_id):
 
     @classmethod
     def get_recently_published(cls, limit):
-        """Returns an iterable with exp summaries that are recently
-        published.
+        """Fetches exp summaries that are recently published.
+
+        Args:
+            limit: int. The maximum number of results to return.
+
+        Returns:
+            An iterable with exp summaries that are recently published. The
+                returned list is sorted by the time of publication with latest
+                being first in the list.
         """
         return ExpSummaryModel.query().filter(
             ndb.OR(ExpSummaryModel.status == feconf.ACTIVITY_STATUS_PUBLIC,


Test patch: