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`


Resolving patch:
diff --git a/core/domain/stats_jobs_continuous.py b/core/domain/stats_jobs_continuous.py
--- a/core/domain/stats_jobs_continuous.py
+++ b/core/domain/stats_jobs_continuous.py
@@ -76,6 +76,18 @@ 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. Currently active realtime datastore
+                layer.
+            event_type: str. The event triggered by a student. For example, when
+                a student starts an exploration, event of type `start` is
+                triggered. If he/she completes an exploration, event of type
+                `complete` is triggered.
+            *args: Variable length argument list. The first element of *args
+                corresponds to the id of the exploration currently being played.
+        """
         exp_id = args[0]
 
         def _increment_visit_counter():
@@ -116,25 +128,32 @@ def _increment_completion_counter():
     # Public query method.
     @classmethod
     def get_statistics(cls, exploration_id, exploration_version):
-        """
+        """Gets the statistics for the specified exploration and version.
+
         Args:
-          - exploration_id: id of the exploration to get statistics for
-          - exploration_version: str. Which version of the exploration to get
-              statistics for; this can be a version number, the string 'all',
-              or the string 'none'.
-
-        Returns a dict with the following keys:
-        - 'start_exploration_count': # of times exploration was started
-        - 'complete_exploration_count': # of times exploration was completed
-        - 'state_hit_counts': a dict containing the hit counts for the states
-           in the exploration. It is formatted as follows:
-            {
-                state_name: {
-                    'first_entry_count': # of sessions which hit this state
-                    'total_entry_count': # of total hits for this state
-                    'no_answer_count': # of hits with no answer for this state
-                }
-            }
+            exploration_id: str. The id of the exploration to get statistics
+                for.
+            exploration_version: str. Which version of the exploration to
+                get statistics for. This can be a version number, the string
+                'all', or the string 'none'.
+
+        Returns:
+            dict. The keys of the dict are:
+                'start_exploration_count': # of times exploration was started.
+                'complete_exploration_count': # of times exploration was
+                    completed.
+                'state_hit_counts': a dict containing the hit counts for the
+                    states in the exploration. It is formatted as follows:
+                    {
+                        state_name: {
+                            'first_entry_count': # of sessions which hit
+                                this state.
+                            'total_entry_count': # of total hits for this
+                                state.
+                            'no_answer_count': # of hits with no answer for
+                                this state.
+                        }
+                    }
         """
         num_starts = 0
         num_completions = 0
@@ -166,9 +185,15 @@ def get_statistics(cls, exploration_id, exploration_version):
 
     @classmethod
     def get_views_multi(cls, exploration_ids):
-        """Given a list of exploration ids, returns a list of view counts
-        for the explorations therein. If any of the exploration_ids are
-        None, the corresponding return value is 0.
+        """Generates the view counts for the given exploration IDs.
+
+        Args:
+            exploration_ids: list(str). The list of exploration IDs to get view
+                counts for.
+
+        Returns:
+            list(int). The number of times each exploration in exploration_ids
+                has been viewed.
         """
         entity_ids = [stats_models.ExplorationAnnotationsModel.get_entity_id(
             exploration_id, VERSION_ALL) for exploration_id in exploration_ids]
@@ -212,6 +237,52 @@ def entity_classes_to_map_over(cls):
 
     @staticmethod
     def map(item):
+        """Implements the map function. Must be declared @staticmethod.
+
+        Args:
+            item: StartExplorationEventLogEntryModel or
+                MaybeLeaveExplorationEventLogEntryModel or
+                CompleteExplorationEventLogEntryModel or
+                StateHitEventLogEntryModel or StateCounterModel. If item is of
+                type StateCounterModel the information yielded corresponds to
+                the various counts related to the exploration. If it is of any
+                LogEntryModel type the information yielded corresponds to the
+                type of the event and other details related to the exploration.
+
+        Yields:
+            tuple. 2-tuple in the form ('exploration_id:version', value).
+                `exploration_id` corresponds to the id of the exploration.
+                `version` corresponds to the version of the exploration.
+                `value`: If item is of type StateCounterModel, the value
+                    yielded is:
+                    {
+                        'type': 'counter',
+                        'exploration_id': The ID of the exploration,
+                        'version': Version of the exploration,
+                        'state_name': Name of current state,
+                        'first_entry_count': Number of times the state was
+                            entered for the first time in a reader session,
+                        'subsequent_entries_count': Number of times the state
+                            was entered for the second time or later in a reader
+                            session,
+                        'resolved_answer_count': Number of times an answer
+                            submitted for this state was subsequently resolved
+                            by an exploration editor and removed from the answer
+                            logs,
+                        'active_answer_count': Number of times an answer was
+                            entered for this state and was not subsequently
+                            resolved by an exploration admin}
+                    If item is of LogEntryModel type, value yielded is:
+                    {
+                        'type': 'event',
+                        'event_type': Type of event triggered,
+                        'session_id': ID of current student's session,
+                        'state_name': Name of current state,
+                        'created_on': How many milliseconds ago the exploration
+                            was created,
+                        'exploration_id': The ID of the exploration,
+                        'version': Version of exploration}
+        """
         if StatisticsMRJobManager._entity_created_before_job_queued(item):
             if isinstance(item, stats_models.StateCounterModel):
                 first_dot_index = item.id.find('.')
@@ -250,6 +321,45 @@ def map(item):
 
     @staticmethod
     def reduce(key, stringified_values):
+        """Updates statistics for the given (exploration, version) and list of
+        events and creates batch model(ExplorationAnnotationsModel) for storing
+        this output.
+
+        Args:
+            key: str. The exploration id and version of the exploration in the
+                form 'exploration_id.version'.
+            stringified_values: list(str). A list of stringified values
+                associated with the given key. It includes information depending
+                on the type of event. If type of event is 'event',
+                an element of stringified_values would be:
+                '{
+                    'type': 'event',
+                    'event_type': Type of event triggered,
+                    'session_id': ID of current student's session,
+                    'state_name': Name of current state,
+                    'created_on': How many milliseconds ago the exploration was
+                        created,
+                    'exploration_id': The ID of the exploration,
+                    'version': Version of exploration}'
+                If type is 'counter', then an element of stringified_values
+                would be of the form:
+                '{
+                    'type': 'counter',
+                    'exploration_id': The ID of the exploration,
+                    'version': Version of the exploration,
+                    'state_name': Name of current state,
+                    'first_entry_count': Number of times the state was entered
+                        for the first time in a reader session,
+                    'subsequent_entries_count': Number of times the state was
+                        entered for the second time or later in a reader
+                        session,
+                    'resolved_answer_count': Number of times an answer
+                        submitted for this state was subsequently resolved by
+                        an exploration admin and removed from the answer logs,
+                    'active_answer_count': Number of times an answer was entered
+                        for this state and was not subsequently resolved by an
+                        exploration admin}'
+        """
         exploration = None
         exp_id, version = key.split(':')
         try:


Test patch: