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`


Resolving patch:
diff --git a/core/domain/stats_domain.py b/core/domain/stats_domain.py
--- a/core/domain/stats_domain.py
+++ b/core/domain/stats_domain.py
@@ -30,15 +30,24 @@ class StateRuleAnswerLog(object):
     specific storage model used.
     """
     def __init__(self, answers):
-        # This dict represents a log of answers that hit this rule and that
-        # have not been resolved. The keys of this dict are the answers encoded
-        # as HTML strings, and the values are integer counts representing how
-        # many times the answer has been entered.
+        """Initializes answers dictionary with answers that hit this rule
+        and have not been resolved.
+
+        Args:
+            answers: dict. The keys of the dict are the answers encoded as HTML
+                strings, and the values are integer counts representing how
+                many times the answer has been entered.
+        """
         self.answers = copy.deepcopy(answers)
 
     @property
     def total_answer_count(self):
-        """Total count of answers for this rule that have not been resolved."""
+        """Total count of answers for this rule that have not been resolved.
+
+        Returns:
+            int. The total number of answers for this rule that
+                have not been resolved.
+        """
         # TODO(sll): Cache this computed property.
         return sum(self.answers.values())
 
@@ -48,17 +57,22 @@ def get_multi_by_multi_explorations(
         """Gets domain objects given a list of exploration and state tuples.
 
         Args:
-            exploration_state_list: a list of exploration ID and state name
-                tuples
-            rule_str_list: a list of rule spec strings which are used to filter
-                the answers matched to the provided explorations and states
-
-        Returns a list of StateRuleAnswerLog objects containing answers matched
-        to each exploration ID-state name. The number of elements returned is
-        the same as the number of elements passed in exploration_state_list. The
-        number of answers in each StateRuleAnswerLog object depends on the
-        number of answers matched against each rule spec strings specified in
-        rule_str_list.
+            exploration_state_list: list(tuple). A list of 2-tuples in the form
+                (exploration_id, state_name).
+            rule_str_list: list(str). A list of rule spec strings. A rule spec
+                string is a string representation of a rule. All answers that
+                are submitted to each of the specified
+                (exploration ID, state name) and also match one or more of the
+                rule spec strings in rule_str_list are returned.
+
+        Returns:
+            list(StateRuleAnswerLog). A list of StateRuleAnswerLog objects
+                containing answers matched to each (exploration ID, state name).
+                The number of elements returned is the same as the number of
+                elements in exploration_state_list. The number of answers in
+                each passed StateRuleAnswerLog object is same as the number of
+                answers matched against each rule spec string specified in
+                rule_str_list.
         """
         # TODO(sll): Should each rule_str be unicode instead?
         answer_log_models_list = (
@@ -74,15 +88,24 @@ def get_multi_by_multi_explorations(
                     else:
                         combined_answers[answer] = frequency
             answer_log_list.append(cls(combined_answers))
+
         return answer_log_list
 
     @classmethod
     def get_multi(cls, exploration_id, rule_data):
         """Gets domain objects corresponding to the given rule data.
+
         Args:
-            exploration_id: the exploration id
-            rule_data: a list of dicts, each with the following keys:
-                (state_name, rule_str).
+            exploration_id: str. The id of the exploration.
+            rule_data: list(dict). A list of dicts, each with the following
+                keys: (state_name, rule_str). `state_name`, a string, is the
+                name of the state whose answer log is to be fetched. `rule_str`
+                is a string representation of the rule. Only the answers that
+                match this rule are returned.
+
+        Returns:
+            list(StateRuleAnswerLog). Returns a list of StateRuleAnswerLog
+                objects corresponding to a given rule data and exploration_id.
         """
         # TODO(sll): Should each rule_str be unicode instead?
         # TODO(bhenning): Combine this with get_multi_by_multi_explorations as
@@ -95,13 +118,33 @@ def get_multi(cls, exploration_id, rule_data):
 
     @classmethod
     def get(cls, exploration_id, state_name, rule_str):
+        """Gets the StateRuleAnswerLog domain object corresponding to the
+        given rule spec string and state_name.
+
+        Args:
+            exploration_id: str. The id of the exploration.
+            state_name: str. The name of the state whose answer log is to be
+                fetched.
+            rule_str: str. A string representation of a rule. Only the answers
+                that match this rule are returned.
+
+        Returns:
+            StateRuleAnswerLog. The answer log domain object corresponding to
+                the given exploration id, state name, and rule spec string.
+        """
         # TODO(sll): Deprecate this method.
         return cls.get_multi_by_multi_explorations(
             [(exploration_id, state_name)], [rule_str])[0]
 
     def get_all_top_answers(self):
-        """Returns a list of (answer, count) sorted by count."""
+        """Returns all the answers sorted by count.
 
+        Returns:
+            list(tuple). A list of 2-tuples in the form (answer, count) sorted
+                by count. `answer` is a string that represents a answer
+                submitted to that state of the exploration. `count` of type int,
+                is the number of times that answer has been given.
+        """
         return sorted(
             self.answers.iteritems(), key=operator.itemgetter(1),
             reverse=True)
@@ -113,7 +156,10 @@ def get_top_answers(self, num_answers_to_return):
             num_answers_to_return: the maximum number of answers to return.
 
         Returns:
-            A list of (answer, count) tuples for the `num_answers_to_return`
-            answers with the highest counts.
+            list(tuple). A list of (answer, count) tuples for the
+                `num_answers_to_return` answers with the highest counts.
+                `answer` is a string that represents a answer submitted to that
+                state of the exploration. `count` of type int, is the number of
+                times that answer has been given.
         """
         return self.get_all_top_answers()[:num_answers_to_return]


Test patch: