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**.
@seanlip Please assign me an issue.I also want to contribute.
can I take #2394 Docstrings for controllers/base.py
@seanlip I am taking core.domain.stats_services
OK, thanks! Assigned you.
hey...I am working on core.domain.stats_services....I can see previous docstrings in it.So, what exactly is incomplete in this project.
Hello @arjunsuri, you can look at the previous PRs' ( alike #3448, #2459 ) to get an idea about expected format. I looked into the file [core.domain.stats_services. ](https://github.com/oppia/oppia/blob/develop/core/domain/stats_services.py) there are still few functions missing Args, Return information which you should document in your PR. If you still have any doubts feel free to ping us in the Gitter channel. 
I would like to work on `core.domain.value_generators_domain`
Sounds good, thanks @meetsha. I've assigned you! 
I would like to work on  `core.domain.user_jobs_continuous`

Sounds good, thanks @shubha1593. I've assigned you! 
I would like to be assigned core.domain.fs_domain :)
Thanks @Wert1996. I've assigned you!
@seanlip I want to take core.domain.stats_services

@arjunsuri, I've assigned you!
@kevinlee12  I was looking into core.domain.stats_services....Can you please tell me what exactly I have to do with documentation as this is my first ever contribution
To start off, I'd recommend taking a look at the other PR's like PR #3448, there's quite a lot of PR's linked so you can use those as samples of what we are looking for!
Actually...I think I am not able to understand the complete code....otherwise I could have done it by now.Can you please help me in any way so that I can understand the written code and not the docstring...so that I can write my own docstring. 
@arjunsuri Hi there! I suggest you using Sublime Text editor or something similar (VS Code, Atom) to look up the definitions throughout the codebase and what they do. If you have any particular question about a function and its use then feel free to ping us on Gitter. Thanks!

Resolving patch:
diff --git a/core/domain/user_jobs_continuous.py b/core/domain/user_jobs_continuous.py
--- a/core/domain/user_jobs_continuous.py
+++ b/core/domain/user_jobs_continuous.py
@@ -69,18 +69,27 @@ def _handle_incoming_event(cls, active_realtime_layer, event_type, *args):
     # Public query methods.
     @classmethod
     def get_recent_notifications(cls, user_id):
-        """Gets a list of recent notifications to show on this user's
+        """Gets a list of recent notifications to show on the user's
         dashboard.
 
-        Returns a 2-tuple. The first element is a float representing the number
-        of milliseconds since the Epoch when the job was queued. The second
-        element is a list of recent updates to explorations and feedback
-        threads; each entry is a dict with keys 'type', 'activity_id',
-        'activity_title', 'last_updated_ms', 'author_id' and 'subject'. Here,
-        'type' is either feconf.UPDATE_TYPE_EXPLORATION_COMMIT or
-        feconf.UPDATE_TYPE_FEEDBACK_MESSAGE, 'activity_id' is the id of the
-        exploration being committed to or to which the feedback thread belongs,
-        and 'activity_title' is the corresponding title.
+        Args:
+            user_id: str. The unique ID of the user.
+
+        Returns:
+            tuple(float, list(dict)). A 2-tuple with following entries:
+            - float. The time, in milliseconds since the Epoch, when the job was
+                queued.
+            - list(dict). A list of recent updates to explorations and feedback
+                threads; Each entry in the list is a dict with keys:
+                - 'type': str. Either feconf.UPDATE_TYPE_EXPLORATION_COMMIT or
+                    feconf.UPDATE_TYPE_FEEDBACK_MESSAGE.
+                - 'activity_id': str. The ID of the exploration being committed
+                    to or to which the feedback thread belongs.
+                - 'activity_title': str. The title of the activity.
+                - 'last_updated_ms': float. The time when the update was made,
+                    in milliseconds since the Epoch.
+                - 'author_id': str. The ID of the author who made the update.
+                - 'subject': str. A brief description of the notification.
         """
         user_model = user_models.UserRecentChangesBatchModel.get(
             user_id, strict=False)
@@ -113,28 +122,34 @@ def _get_most_recent_activity_commits(
                 as exp_models.ExplorationModel.
             activity_ids_list: A list of activity IDs (such as exploration IDS)
                 for which the latest commits will be retrieved.
-            activity_type: The type (string) of activity being referenced, such
+            activity_type: str. The type of activity being referenced, such
                 as 'exploration' or 'collection'.
-            commit_type: This (string) represents the activity update commit
+            commit_type: str. This represents the activity update commit
                 type, such as feconf.UPDATE_TYPE_EXPLORATION_COMMIT.
-            delete_commit_message: This (string) represents the commit message
+            delete_commit_message: str. This represents the commit message
                 to use when an activity is found to be deleted, such as
                 feconf.COMMIT_MESSAGE_EXPLORATION_DELETED.
 
         Returns:
-            A tuple with two entries:
-                - A list (one entry per activity ID) of dictionaries with the
-                  following keys:
-                    - type: The value of the commit_type argument.
-                    - activity_id: The ID of the activity for this commit.
-                    - activity_title: The title of the activity.
-                    - author_id: The author who made the commit.
-                    - last_update_ms: When the commit was created.
-                    - subject: The commit message, otherwise (if the activity
-                      has been deleted) a message indicating that the activity
-                      was deleted.
-                - A list containing valid activity model instances which are
-                  mappable to feedback threads
+            tuple(list(dict), list(ExplorationModel|CollectionModel)). A 2-tuple
+            with following entries:
+                - list(dict): A list, having information for every activity in
+                    activity_ids_list. Each dict in this list has the following
+                    keys:
+                    - 'type': str. The value of the commit_type argument.
+                    - 'activity_id': str. The ID of the activity for this
+                        commit.
+                    - 'activity_title': str. The title of the activity.
+                    - 'author_id': str. The ID of the author who made the
+                        commit.
+                    - 'last_update_ms': float. The time when the commit was
+                        created, in milliseconds since Epoch.
+                    - 'subject': str. The commit message, or (if the activity
+                        has been deleted) a message indicating that the activity
+                        was deleted.
+                - list(ExplorationModel|CollectionModel): A list containing
+                    valid Exploration or Collection model instances which are
+                    mappable to feedback threads.
         """
         most_recent_commits = []
         activity_models = activity_model_cls.get_multi(
@@ -189,6 +204,28 @@ def entity_classes_to_map_over(cls):
 
     @staticmethod
     def map(item):
+        """Implements the map function (generator).
+        Computes most recent activity commits and feedbacks of a specific user.
+
+        Args:
+            item: UserSubscriptionsModel. An instance of UserSubscriptionsModel.
+
+        Yields:
+            This function may yield as many times as appropriate (including
+            zero) 2-tuples in the format (str, dict), where
+                - str: A key of the form 'user_id@job_queued_msec'.
+                - dict: A dictionary with the following keys:
+                    - 'type': str. Either feconf.UPDATE_TYPE_EXPLORATION_COMMIT
+                        or feconf.UPDATE_TYPE_FEEDBACK_MESSAGE.
+                    - 'activity_id': str. The ID of the exploration being
+                        committed to or to which the feedback thread belongs.
+                    - 'activity_title': str. The title of the activity.
+                    - 'last_updated_ms': float. The time when the update was
+                        made, in milliseconds since the Epoch.
+                    - 'author_id': str. The ID of the author who made the
+                        update.
+                    - 'subject': str. A brief description of the recent updates.
+        """
         user_id = item.id
         job_queued_msec = RecentUpdatesMRJobManager._get_job_queued_msec()
         reducer_key = '%s@%s' % (user_id, job_queued_msec)
@@ -244,6 +281,28 @@ def map(item):
 
     @staticmethod
     def reduce(key, stringified_values):
+        """Implements the reduce function.
+
+        This function updates an instance of UserRecentChangesBatchModel
+        for a particular user, storing the most recent changes corresponding
+        to things the user (for the given user_id) subscribes to.
+
+        Args:
+            key: str. Should be of the form 'user_id@job_queued_msec'.
+            stringified_values: list(str). A list of all recent_activity_commits
+                and feedback_threads that are tagged with the given key. Each
+                entry is a stringified dict having the following keys:
+                - 'type': str. Either feconf.UPDATE_TYPE_EXPLORATION_COMMIT or
+                    feconf.UPDATE_TYPE_FEEDBACK_MESSAGE.
+                - 'activity_id': str. The ID of the exploration being committed
+                    to or to which the feedback thread belongs.
+                - 'activity_title': str. The title of the activity.
+                - 'last_updated_ms': float. The time when the changes were made,
+                    in milliseconds since the Epoch.
+                - 'author_id': str. The ID of the author who made the changes.
+                - 'subject': str. The commit message or message indicating a
+                    feedback update.
+        """
         if '@' not in key:
             logging.error(
                 'Invalid reducer key for RecentUpdatesMRJob: %s' % key)
@@ -293,6 +352,27 @@ def _get_batch_job_manager_class(cls):
 
     @classmethod
     def _handle_incoming_event(cls, active_realtime_layer, event_type, *args):
+        """Records incoming events in the given realtime layer.
+
+        Args:
+            active_realtime_layer: int. The currently active realtime datastore
+                layer.
+            event_type: str. The event triggered by a student. For example, when
+                a student starts an exploration, an event of type `start` is
+                triggered and the total play count is incremented. If he/she
+                rates an exploration, an event of type `rate` is triggered and
+                average rating of the realtime model is refreshed.
+            *args:
+                If event_type is 'start', then this is a 1-element list with
+                following entry:
+                - str. The ID of the exploration currently being played.
+                If event_type is 'rate_exploration', then this is a 3-element
+                list with following entries:
+                - str. The ID of the exploration currently being played.
+                - float. The rating given by user to the exploration.
+                - float. The old rating of the exploration, before it is
+                    refreshed.
+        """
         exp_id = args[0]
 
         def _refresh_average_ratings(user_id, rating, old_rating):
@@ -353,12 +433,15 @@ def _increment_total_plays_count(user_id):
     def get_dashboard_stats(cls, user_id):
         """
         Args:
-          - user_id: id of the exploration to get statistics for
+            user_id: str. The ID of the user.
 
         Returns a dict with the following keys:
-        - 'total_plays': # of times the user's explorations were played
-        - 'num_ratings': # of times the user's explorations were started
-        - 'average_ratings': average of average ratings across all explorations
+            'total_plays': int. Number of times the user's explorations were
+                played.
+            'num_ratings': int. Number of times the explorations have been
+                rated.
+            'average_ratings': float. Average of average ratings across all
+                explorations.
         """
         total_plays = 0
         num_ratings = 0
@@ -418,6 +501,29 @@ def entity_classes_to_map_over(cls):
 
     @staticmethod
     def map(item):
+        """Implements the map function (generator).
+        Computes exploration data for every contributor and owner of the
+        exploration.
+
+        Args:
+            item: ExpSummaryModel. An instance of ExpSummaryModel.
+
+        Yields:
+            This function may yield as many times as appropriate 2-tuples in the
+            format (str, dict), where
+            - str. The unique ID of the user.
+            - dict: A dict that includes entries for all the explorations that
+                this user contributes to or owns. Each entry has the following
+                keys:
+                - 'exploration_impact_score': float. The impact score of all the
+                    explorations contributed to by the user.
+                - 'total_plays_for_owned_exp': int. Total plays of all
+                    explorations owned by the user.
+                - 'average_rating_for_owned_exp': float. Average of average
+                    ratings of all explorations owned by the user.
+                - 'num_ratings_for_owned_exp': int. Total number of ratings of
+                    all explorations owned by the user.
+        """
         if item.deleted:
             return
 
@@ -520,6 +626,27 @@ def map(item):
 
     @staticmethod
     def reduce(key, stringified_values):
+        """Implements the reduce function.
+
+        This function creates or updates the UserStatsModel instance for the
+        given user. It updates the impact score, total plays of all
+        explorations, number of ratings across all explorations and average
+        rating.
+
+        Args:
+            key: str. The unique ID of the user.
+            stringified_values: list(str). A list of information regarding all
+                the explorations that this user contributes to or owns. Each
+                entry is a stringified dict having the following keys:
+                - 'exploration_impact_score': float. The impact score of all the
+                    explorations contributed to by the user.
+                - 'total_plays_for_owned_exp': int. Total plays of all
+                    explorations owned by the user.
+                - 'average_rating_for_owned_exp': float. Average of average
+                    ratings of all explorations owned by the user.
+                - 'num_ratings_for_owned_exp': int. Total number of ratings of
+                    all explorations owned by the user.
+        """
         values = [ast.literal_eval(v) for v in stringified_values]
         exponent = 2.0/3
 


Test patch: