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/domain/exp_services.py b/core/domain/exp_services.py
--- a/core/domain/exp_services.py
+++ b/core/domain/exp_services.py
@@ -78,10 +78,13 @@ def _migrate_states_schema(versioned_exploration_states):
 
     Args:
         versioned_exploration_states: A dict with two keys:
-          - states_schema_version: the states schema version for the
-            exploration.
-          - states: the dict of states comprising the exploration. The keys in
-            this dict are state names.
+            states_schema_version: int. the states schema version for the
+                exploration.
+            states: the dict of states comprising the exploration. The keys in
+                this dict are state names.
+
+    Raises:
+        Exception: The given states_schema_version is invalid.
     """
     states_schema_version = versioned_exploration_states[
         'states_schema_version']
@@ -104,7 +107,18 @@ def _migrate_states_schema(versioned_exploration_states):
 
 # Repository GET methods.
 def _get_exploration_memcache_key(exploration_id, version=None):
-    """Returns a memcache key for an exploration."""
+    """Returns a memcache key for an exploration.
+
+    Args:
+        exploration_id: str. The id of the exploration whose memcache key
+            is to be returned.
+        version: int or None. If specified, the version of the exploration
+            whose memcache key is to be returned.
+
+    Returns:
+        str. Memcache key for the given exploration (or exploration version).
+    """
+
     if version:
         return 'exploration-version:%s:%s' % (exploration_id, version)
     else:
@@ -123,6 +137,15 @@ def get_exploration_from_model(exploration_model, run_conversion=True):
     IMPORTANT NOTE TO DEVELOPERS: In general, run_conversion should never be
     False. This option is only used for testing that the states schema version
     migration works correctly, and it should never be changed otherwise.
+
+    Args:
+        exploration_model: An exploration storage model.
+        run_conversion: bool. When True, updates the exploration to the latest
+            states_schema_version if necessary.
+
+    Returns:
+       Exploration. The exploration domain object corresponding to the given
+       exploration model.
     """
 
     # Ensure the original exploration model does not get altered.
@@ -152,6 +175,17 @@ def get_exploration_from_model(exploration_model, run_conversion=True):
 
 
 def get_exploration_summary_from_model(exp_summary_model):
+    """Returns an ExplorationSummary domain object.
+
+    Args:
+        exp_summary_model: ExplorationSummary. An ExplorationSummary model
+            instance.
+
+    Returns:
+        ExplorationSummary. The summary domain object correspoding to the
+        given exploration summary model.
+    """
+
     return exp_domain.ExplorationSummary(
         exp_summary_model.id, exp_summary_model.title,
         exp_summary_model.category, exp_summary_model.objective,
@@ -169,7 +203,19 @@ def get_exploration_summary_from_model(exp_summary_model):
 
 
 def get_exploration_by_id(exploration_id, strict=True, version=None):
-    """Returns a domain object representing an exploration."""
+    """Returns an Exploration domain object.
+
+    Args:
+        exploration_id: str. The id of the exploration to be returned.
+        strict: bool. Whether to fail noisily if no exploration with a given id
+            exists.
+        version: int or None. The version of the exploration to be returned.
+            If None, the latest version of the exploration is returned.
+
+    Returns:
+        Exploration. The domain object corresponding to the given exploration.
+    """
+
     exploration_memcache_key = _get_exploration_memcache_key(
         exploration_id, version=version)
     memcached_exploration = memcache_services.get_multi(
@@ -190,7 +236,15 @@ def get_exploration_by_id(exploration_id, strict=True, version=None):
 
 
 def get_exploration_summary_by_id(exploration_id):
-    """Returns a domain object representing an exploration summary."""
+    """Returns a domain object representing an exploration summary.
+
+    Args:
+        exploration_id: str. The id of the ExplorationSummary to be returned.
+
+    Returns:
+        ExplorationSummary. The summary domain object corresponding to the
+        given exploration.
+    """
     # TODO(msl): Maybe use memcache similarly to get_exploration_by_id.
     exp_summary_model = exp_models.ExpSummaryModel.get(
         exploration_id)
@@ -203,8 +257,21 @@ def get_exploration_summary_by_id(exploration_id):
 
 def get_multiple_explorations_by_id(exp_ids, strict=True):
     """Returns a dict of domain objects representing explorations with the
-    given ids as keys. If an exp_id is not present it is not included in the
+    given ids as keys. If an exp_id is not present, it is not included in the
     return dict.
+
+    Args:
+        exp_ids: list(str). List of ids of the exploration to be returned.
+        strict: bool. If True, a ValueError is raised when any exploration id
+            is invalid.
+
+    Returns:
+        dict. Maps exploration ids to the corresponding Exploration domain
+        objects. Any invalid exploration ids are omitted.
+
+    Raises:
+        ValueError: When strict is True and at least one of the given exp_ids
+        is invalid.
     """
     exp_ids = set(exp_ids)
     result = {}
@@ -250,13 +317,23 @@ def get_multiple_explorations_by_id(exp_ids, strict=True):
 
 
 def get_new_exploration_id():
-    """Returns a new exploration id."""
+    """Returns a new exploration id.
+
+    Returns:
+        str. A new exploration id.
+    """
     return exp_models.ExplorationModel.get_new_id('')
 
 
 def is_exp_summary_editable(exp_summary, user_id=None):
-    """Checks if a given user may edit an exploration by checking
-    the given domain object.
+    """Checks if a given user has permissions to edit the exploration.
+
+    Args:
+        exp_summary: ExplorationSummary. An ExplorationSummary domain object.
+        user_id: str. The id of the user whose permissions are being checked.
+
+    Returns:
+        bool. Whether the user has permissions to edit the exploration.
     """
     return user_id is not None and (
         user_id in exp_summary.editor_ids
@@ -273,6 +350,15 @@ def get_exploration_titles_and_categories(exp_ids):
 
     Any invalid exp_ids will not be included in the return dict. No error will
     be raised.
+
+    Args:
+        exp_ids: list(str). A list of exploration ids of exploration domain
+            objects.
+
+    Returns:
+        dict. The keys are exploration ids and the corresponding values are
+        dicts with the keys 'title' and 'category'. Any invalid exploration
+        ids are excluded.
     """
     explorations = [
         (get_exploration_from_model(e) if e else None)
@@ -292,8 +378,16 @@ def get_exploration_titles_and_categories(exp_ids):
 
 
 def _get_exploration_summaries_from_models(exp_summary_models):
-    """Given an iterable of ExpSummaryModel instances, create a dict containing
-    corresponding exploration summary domain objects, keyed by id.
+    """Returns a dict with ExplorationSummary domain objects as values,
+    keyed by their exploration id.
+
+    Args:
+        exp_summary_models: list(ExplorationSummary). List of ExplorationSummary
+            model instances.
+
+    Returns:
+        dict. The keys are exploration ids and the values are the corresponding
+        ExplorationSummary domain objects.
     """
     exploration_summaries = [
         get_exploration_summary_from_model(exp_summary_model)
@@ -305,9 +399,17 @@ def _get_exploration_summaries_from_models(exp_summary_models):
 
 
 def get_exploration_summaries_matching_ids(exp_ids):
-    """Given a list of exploration ids, return a list with the corresponding
-    summary domain objects (or None if the corresponding summary does not
-    exist).
+    """Returns a list of ExplorationSummary domain objects (or None if the
+    corresponding summary does not exist) corresponding to the given
+    list of exploration ids.
+
+    Args:
+        exp_ids: list(str). List of exploration ids.
+
+    Returns:
+        list(ExplorationSummary|None). List of ExplorationSummary domain objects
+        corresponding to the given exploration ids. If an ExplorationSummary
+        does not exist, the corresponding returned list element is None.
     """
     return [
         (get_exploration_summary_from_model(model) if model else None)
@@ -322,6 +424,15 @@ def get_exploration_ids_matching_query(query_string, cursor=None):
     there are at least that many, otherwise it returns all remaining results.
     (If this behaviour does not occur, an error will be logged.) The method
     also returns a search cursor.
+
+    Args:
+        query_string: str. A search query string.
+        cursor: str or None. Optional cursor from which to start the search
+            query. If no cursor is supplied, the first N results matching
+            the query are returned.
+
+    Returns:
+        list(str). A list of exploration ids matching the given search query.
     """
     returned_exploration_ids = []
     search_cursor = cursor
@@ -361,22 +472,45 @@ def get_exploration_ids_matching_query(query_string, cursor=None):
 def get_non_private_exploration_summaries():
     """Returns a dict with all non-private exploration summary domain objects,
     keyed by their id.
+
+    Returns:
+        dict. The keys are exploration ids and the values are corresponding
+        non-private ExplorationSummary domain objects.
     """
     return _get_exploration_summaries_from_models(
         exp_models.ExpSummaryModel.get_non_private())
 
 
 def get_top_rated_exploration_summaries(limit):
-    """Returns a dict with top rated exploration summary domain objects,
-    keyed by their id.
+    """Returns a dict with top rated exploration summary model instances,
+    keyed by their id. At most 'limit' entries are returned.
+
+    Args:
+        limit: int. The maximum number of exploration summary model instances to
+            be returned.
+
+    Returns:
+        dict. The keys are exploration ids and the values are the corresponding
+        top rated ExplorationSummary domain model instances.  At most limit
+        entries are returned.
     """
     return _get_exploration_summaries_from_models(
         exp_models.ExpSummaryModel.get_top_rated(limit))
 
 
 def get_recently_published_exp_summaries(limit):
-    """Returns a dict with all featured exploration summary domain objects,
-    keyed by their id.
+    """Returns a dict with recently published ExplorationSummary model
+    instances, keyed by their exploration id. At most 'limit' entries are
+    returned.
+
+    Args:
+        limit: int. The maximum number of exploration summary model instances to
+            be returned.
+
+    Returns:
+        dict. The dict contains recently published ExplorationSummary model
+            instances as a value keyed by their exploration id. At most 'limit'
+            entries are returned.
     """
     return _get_exploration_summaries_from_models(
         exp_models.ExpSummaryModel.get_recently_published(limit))
@@ -385,6 +519,10 @@ def get_recently_published_exp_summaries(limit):
 def get_all_exploration_summaries():
     """Returns a dict with all exploration summary domain objects,
     keyed by their id.
+
+    Returns:
+        dict. A dict with all ExplorationSummary domain objects keyed by their
+        exploration id.
     """
     return _get_exploration_summaries_from_models(
         exp_models.ExpSummaryModel.get_all())
@@ -392,7 +530,18 @@ def get_all_exploration_summaries():
 
 # Methods for exporting states and explorations to other formats.
 def export_to_zip_file(exploration_id, version=None):
-    """Returns a ZIP archive of the exploration."""
+    """Returns a ZIP archive of the exploration.
+
+    Args:
+        exploration_id: str. The id of the exploration to export.
+        version: int or None. If provided, this indicates which version of
+            the exploration to export. Otherwise, the latest version of the
+            exploration is exported.
+
+    Returns:
+        str. The contents of the ZIP archive of the exploration (which can be
+        subsequently converted into a zip file via zipfile.ZipFile()).
+    """
     exploration = get_exploration_by_id(exploration_id, version=version)
     yaml_repr = exploration.to_yaml()
 
@@ -421,6 +570,19 @@ def export_to_zip_file(exploration_id, version=None):
 
 
 def convert_state_dict_to_yaml(state_dict, width):
+    """Converts the given state dict to yaml format.
+
+    Args:
+        state_dict: dict. A dict representing a state in an exploration.
+        width: int. The maximum number of characters in a line for the
+            returned YAML string.
+
+    Returns:
+        str. The YAML version of the state_dict.
+
+    Raises:
+        Exception: The state_dict does not represent a valid state.
+    """
     try:
         # Check if the state_dict can be converted to a State.
         state = exp_domain.State.from_dict(state_dict)
@@ -432,9 +594,17 @@ def convert_state_dict_to_yaml(state_dict, width):
 
 
 def export_states_to_yaml(exploration_id, version=None, width=80):
-    """Returns a python dictionary of the exploration, whose keys are state
+    """Returns a dictionary of the exploration, whose keys are state
     names and values are yaml strings representing the state contents with
     lines wrapped at 'width' characters.
+
+    Args:
+        exploration_id: str. The id of the exploration whose states should
+            be exported.
+
+    Returns:
+        dict. The keys are state names, and the values are YAML strings
+        representing the corresponding state's contents.
     """
     exploration = get_exploration_by_id(exploration_id, version=version)
     exploration_dict = {}
@@ -451,8 +621,17 @@ def apply_change_list(exploration_id, change_list):
     Each entry in change_list is a dict that represents an ExplorationChange
     object.
 
+    Args:
+        exploration_id: str. The id of the exploration to which the change list
+            is to be applied.
+        change_list: list(dict). The list of changes to apply.
+
     Returns:
-      the resulting exploration domain object.
+        Exploration. The exploration domain object that results from applying
+        the given changelist to the existing version of the exploration.
+
+    Raises:
+        Exception: Any entries in the changelist are invalid.
     """
     exploration = get_exploration_by_id(exploration_id)
     try:
@@ -569,6 +748,17 @@ def _save_exploration(committer_id, exploration, commit_message, change_list):
 
     If successful, increments the version number of the incoming exploration
     domain object by 1.
+
+    Args:
+        committer_id: str. The id of the user who made the commit.
+        exploration: Exploration. The exploration to be saved.
+        commit_message: str. The commit message.
+        change_list: list(ExplorationChange). A list of changes introduced in
+            this commit.
+
+    Raises:
+        Exception: The versions of the given exploration and the currently
+            stored exploration model do not match.
     """
     if change_list is None:
         change_list = []
@@ -625,6 +815,16 @@ def _create_exploration(
 
     This is because _save_exploration() depends on the rights object being
     present to tell it whether to do strict validation or not.
+
+    Args:
+        committer_id: str. The id of the user who made the commit.
+        exploration: Exploration. The exploration domain object.
+        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.
     """
     # This line is needed because otherwise a rights object will be created,
     # but the creation of an exploration object will fail.
@@ -655,6 +855,12 @@ def _create_exploration(
 
 
 def save_new_exploration(committer_id, exploration):
+    """Saves a newly created exploration.
+
+    Args:
+        committer_id: str. The id of the user who made the commit.
+        exploration: Exploration. The exploration domain object to be saved.
+    """
     commit_message = (
         ('New exploration created with title \'%s\'.' % exploration.title)
         if exploration.title else 'New exploration created.')
@@ -678,6 +884,13 @@ def delete_exploration(committer_id, exploration_id, force_deletion=False):
     and are unrecoverable. Otherwise, the exploration and all its history are
     marked as deleted, but the corresponding models are still retained in the
     datastore. This last option is the preferred one.
+
+    Args:
+        committer_id: str. The id of the user who made the commit.
+        exploration_id: str. The id of the exploration to be deleted.
+        force_deletion: bool. If True, completely deletes the storage models
+            corresponding to the exploration. Otherwise, marks them as deleted
+            but keeps the corresponding models in the datastore.
     """
     # TODO(sll): Delete the files too?
 
@@ -714,14 +927,15 @@ def get_exploration_snapshots_metadata(exploration_id):
     """Returns the snapshots for this exploration, as dicts.
 
     Args:
-        exploration_id: str. The id of the exploration in question.
+        exploration_id: str. The id of the exploration whose snapshots_metadata
+            is required.
 
     Returns:
-        list of dicts, each representing a recent snapshot. Each dict has the
-        following keys: committer_id, commit_message, commit_cmds, commit_type,
-        created_on_ms, version_number. The version numbers are consecutive and
-        in ascending order. There are exploration.version_number items in the
-        returned list.
+        list(dict). List of dicts, each representing a recent snapshot. Each
+        dict has the following keys: committer_id, commit_message, commit_cmds,
+        commit_type, created_on_ms, version_number. The version numbers are
+        consecutive and in ascending order. There are exploration.version_number
+        items in the returned list.
     """
     exploration = get_exploration_by_id(exploration_id)
     current_version = exploration.version
@@ -734,6 +948,13 @@ def get_exploration_snapshots_metadata(exploration_id):
 def _get_last_updated_by_human_ms(exp_id):
     """Return the last time, in milliseconds, when the given exploration was
     updated by a human.
+
+    Args:
+        exp_id: str. The id of the exploration.
+
+    Returns:
+        float. The last time in milliseconds when a given exploration was
+        updated by a human.
     """
     # Iterate backwards through the exploration history metadata until we find
     # the most recent snapshot that was committed by a human.
@@ -753,6 +974,10 @@ def publish_exploration_and_update_user_profiles(committer_id, exp_id):
 
     It is the responsibility of the caller to check that the exploration is
     valid prior to publication.
+
+    Args:
+        committer_id: str. The id of the user who made the commit.
+        exp_id: str. The id of the exploration to be published.
     """
     rights_manager.publish_exploration(committer_id, exp_id)
     contribution_time_msec = utils.get_current_time_in_millisecs()
@@ -768,18 +993,27 @@ def update_exploration(
     """Update an exploration. Commits changes.
 
     Args:
-    - committer_id: str. The id of the user who is performing the update
-        action.
-    - exploration_id: str. The exploration id.
-    - change_list: list of dicts, each representing a _Change object. These
-        changes are applied in sequence to produce the resulting exploration.
-    - commit_message: str or None. A description of changes made to the state.
-        For published explorations, this must be present; for unpublished
-        explorations, it should be equal to None. For suggestions that are
-        being accepted, and only for such commits, it should start with
-        feconf.COMMIT_MESSAGE_ACCEPTED_SUGGESTION_PREFIX.
-    - is_suggestion: bool. whether the update is due to a suggestion being
-        accepted.
+        committer_id: str. The id of the user who is performing the update
+            action.
+        exploration_id: str. The id of the exploration to be updated.
+        change_list: list(dict). A change list to be applied to the given
+            exploration. Each entry in change_list is a dict that represents an
+            ExplorationChange.
+        commit_message: str or None. A description of changes made to the state.
+            For published explorations, this must be present; for unpublished
+            explorations, it should be equal to None. For suggestions that are
+            being accepted, and only for such commits, it should start with
+            feconf.COMMIT_MESSAGE_ACCEPTED_SUGGESTION_PREFIX.
+        is_suggestion: bool. Whether the update is due to a suggestion being
+            accepted.
+
+    Raises:
+        ValueError: No commit message is supplied and the exploration is public.
+        ValueError: The update is due to a suggestion and the commit message is
+            invalid.
+        ValueError: The update is not due to a suggestion, and the commit
+            message starts with the same prefix as the commit message for
+            accepted suggestions.
     """
     is_public = rights_manager.is_exploration_public(exploration_id)
     if is_public and not commit_message:
@@ -813,7 +1047,16 @@ def update_exploration(
 
 
 def create_exploration_summary(exploration_id, contributor_id_to_add):
-    """Create summary of an exploration and store in datastore."""
+    """Create the summary model for an exploration, and store it in the
+    datastore.
+
+    Args:
+        exploration_id: str. The id of the exploration.
+        contributor_id_to_add: str or None. The user_id of user who have
+            created the exploration will be added to the list of contributours
+            for the exploration if the argument is not None and it is not a
+            system id.
+    """
     exploration = get_exploration_by_id(exploration_id)
     exp_summary = compute_summary_of_exploration(
         exploration, contributor_id_to_add)
@@ -821,7 +1064,17 @@ def create_exploration_summary(exploration_id, contributor_id_to_add):
 
 
 def update_exploration_summary(exploration_id, contributor_id_to_add):
-    """Update the summary of an exploration."""
+    """Update the summary of an exploration.
+
+    Args:
+        exploration_id: str. The id of the exploration whose summary is
+            to be updated.
+        contributor_id_to_add: str or None. The user_id of user who have
+            contributed (humans who have made a positive (not just a revert)
+            update to the exploration's content) will be added to the list of
+            contributours for the exploration if the argument is not None and it
+            is not a system id.
+    """
     exploration = get_exploration_by_id(exploration_id)
     exp_summary = compute_summary_of_exploration(
         exploration, contributor_id_to_add)
@@ -833,6 +1086,18 @@ def compute_summary_of_exploration(exploration, contributor_id_to_add):
     domain object and return it. contributor_id_to_add will be added to
     the list of contributors for the exploration if the argument is not
     None and if the id is not a system id.
+
+    Args:
+        exploration: Exploration. The exploration whose summary is to be
+            computed.
+        contributor_id_to_add: str or None. The user_id of user who have
+            contributed (humans who have made a positive (not just a revert)
+            change to the exploration's content) will be added to the list of
+            contributours for the exploration if the argument is not None and it
+            is not a system id.
+
+    Returns:
+        ExplorationSummary. The resulting exploration summary domain object.
     """
     exp_rights = exp_models.ExplorationRightsModel.get_by_id(exploration.id)
     exp_summary_model = exp_models.ExpSummaryModel.get_by_id(exploration.id)
@@ -889,6 +1154,15 @@ def compute_exploration_contributors_summary(exploration_id):
     """Returns a dict whose keys are user_ids and whose values are
     the number of (non-revert) commits made to the given exploration
     by that user_id. This does not count commits which have since been reverted.
+
+    Args:
+        exploration_id: str. The id of the exploration.
+
+    Returns:
+        dict. The keys are all user_ids who have made commits to the given
+        exploration. The corresponding values are the number of commits made by
+        each user. Commits that revert to an earlier version, or forward
+        commits which have since been reverted, are excluded.
     """
     snapshots_metadata = get_exploration_snapshots_metadata(exploration_id)
     current_version = len(snapshots_metadata)
@@ -913,6 +1187,9 @@ def compute_exploration_contributors_summary(exploration_id):
 def save_exploration_summary(exp_summary):
     """Save an exploration summary domain object as an ExpSummaryModel entity
     in the datastore.
+
+    Args:
+        exp_summary: ExplorationSummary. The exploration summary to save.
     """
     exp_summary_model = exp_models.ExpSummaryModel(
         id=exp_summary.id,
@@ -943,14 +1220,32 @@ def save_exploration_summary(exp_summary):
 
 
 def delete_exploration_summary(exploration_id):
-    """Delete an exploration summary model."""
+    """Delete an exploration summary model.
+
+    Args:
+        exploration_id: str. The id of the exploration summary to be
+            deleted.
+    """
 
     exp_models.ExpSummaryModel.get(exploration_id).delete()
 
 
 def revert_exploration(
         committer_id, exploration_id, current_version, revert_to_version):
-    """Reverts an exploration to the given version number. Commits changes."""
+    """Reverts an exploration to the given version number. Commits changes.
+
+    Args:
+        committer_id: str. The id of the user who made the commit.
+        exploration_id: str. The id of the exploration to be reverted to the
+            current version.
+        current_version: int. The current version of the exploration.
+        revert_to_version: int. The version to which the given exploration
+            is to be reverted.
+
+    Raises:
+        Exception:  does not match the version of the currently-stored
+            exploration model.
+    """
     exploration_model = exp_models.ExplorationModel.get(
         exploration_id, strict=False)
 
@@ -991,13 +1286,17 @@ def get_demo_exploration_components(demo_path):
     """Gets the content of `demo_path` in the sample explorations folder.
 
     Args:
-      demo_path: the file or folder path for the content of an exploration
-        in SAMPLE_EXPLORATIONS_DIR. E.g.: 'adventure.yaml' or 'tar/'.
+        demo_path: str. The file or folder path for the content of an
+            exploration in SAMPLE_EXPLORATIONS_DIR. E.g.: 'adventure.yaml' or
+            'tar/'.
 
     Returns:
-      a 2-tuple, the first element of which is a yaml string, and the second
-      element of which is a list of (filepath, content) 2-tuples. The filepath
-      does not include the assets/ prefix.
+        tuple. A 2-tuple, the first element of which is a yaml string, and the
+        second element of which is a list of (filepath, content) 2-tuples. The
+        filepath does not include the assets/ prefix.
+
+    Raises:
+        Exception: The path of the file is unrecognized or does not exist.
     """
     demo_filepath = os.path.join(feconf.SAMPLE_EXPLORATIONS_DIR, demo_path)
 
@@ -1017,6 +1316,16 @@ def save_new_exploration_from_yaml_and_assets(
     exp_domain.Exploration.LAST_UNTITLED_SCHEMA_VERSION,
     since in that case the YAML schema will not have a title and category
     present.
+
+    Args:
+        committer_id: str. The id of the user who made the commit.
+        yaml_content: str. The YAML representation of the exploration.
+        exploration_id: str. The id of the exploration.
+        assets_list: list(list(str)). A list of lists of assets, which contains
+            asset's filename and content.
+
+    Raises:
+        Exception: The yaml file is invalid due to a missing schema version.
     """
     if assets_list is None:
         assets_list = []
@@ -1054,7 +1363,14 @@ def save_new_exploration_from_yaml_and_assets(
 
 
 def delete_demo(exploration_id):
-    """Deletes a single demo exploration."""
+    """Deletes a single demo exploration.
+
+    Args:
+        exploration_id: str. The id of the exploration to be deleted.
+
+    Raises:
+        Exception: The exploration id is invalid.
+    """
     if not exp_domain.Exploration.is_demo_exploration_id(exploration_id):
         raise Exception('Invalid demo exploration id %s' % exploration_id)
 
@@ -1070,8 +1386,14 @@ def delete_demo(exploration_id):
 def load_demo(exploration_id):
     """Loads a demo exploration.
 
-    The resulting exploration will have two commits in its history (one for its
-    initial creation and one for its subsequent modification.)
+    The resulting exploration will have two commits in its history (one for
+    its initial creation and one for its subsequent modification.)
+
+    Args:
+        exploration_id: str. The id of the demo exploration.
+
+    Raises:
+        Exception: The exploration id provided is invalid.
     """
     delete_demo(exploration_id)
 
@@ -1097,10 +1419,25 @@ def get_next_page_of_all_commits(
         page_size=feconf.COMMIT_LIST_PAGE_SIZE, urlsafe_start_cursor=None):
     """Returns a page of commits to all explorations in reverse time order.
 
-    The return value is a triple (results, cursor, more) as described in
+    The return value is a tuple (results, cursor, more) as described in
     fetch_page() at:
 
         https://developers.google.com/appengine/docs/python/ndb/queryclass
+
+    Args:
+        page_size: int. The maximum number of commits to return.
+        urlsafe_start_cursor: str. If this is not None, then the returned
+            commits start from the cursor location. Otherwise they start from
+            the beginning of the list of commits.
+
+    Returns:
+        tuple. A 3-tuple consisting of:
+            - list(ExplorationCommitLogEntry). A list containing
+              ExplorationCommitlogEntry domain objects.
+            - str. The postion of the cursor.
+            - bool. indicating whether there are (likely) more results after
+              this batch. If False, there are no more results; if True, there
+              are probably more results.
     """
     results, new_urlsafe_start_cursor, more = (
         exp_models.ExplorationCommitLogEntryModel.get_all_commits(
@@ -1120,10 +1457,27 @@ def get_next_page_of_all_non_private_commits(
     """Returns a page of non-private commits in reverse time order. If max_age
     is given, it should be a datetime.timedelta instance.
 
-    The return value is a triple (results, cursor, more) as described in
+    The return value is a tuple (results, cursor, more) as described in
     fetch_page() at:
 
         https://developers.google.com/appengine/docs/python/ndb/queryclass
+
+    Args:
+        page_size: int. Number of commits that are in the commit list page.
+        urlsafe_start_cursor: str. If this is not None, then the returned
+            commits start from cursor location. Otherwise they start from the
+            beginning of the list of commits.
+
+    Returns:
+        tuple. A 3-tuple consisting of:
+            - list(ExplorationCommitLogEntry). A list containing
+              ExplorationCommitlogEntry domain objects.
+            - str. The postion of the cursor.
+            - bool. indicating whether there are (likely) more results after
+              this batch. If False, there are no more results; if True, there
+              are probably more results.
+    Raises:
+        ValueError: The argument max_age is not datetime.timedelta or None.
     """
     if max_age is not None and not isinstance(max_age, datetime.timedelta):
         raise ValueError(
@@ -1143,6 +1497,18 @@ def get_next_page_of_all_non_private_commits(
 
 def _exp_rights_to_search_dict(rights):
     # Allow searches like "is:featured".
+    """Returns a search dict with information about the exploration rights. This
+    allows searches like "is:featured".
+
+    Args:
+        rights: ActivityRights. Domain object for the rights/publication status
+            of the exploration.
+
+    Returns:
+        dict. If the status of the given exploration is publicized then it
+        returns a dict with a key "is", and the value "featured". Otherwise, it
+        returns an empty dict.
+    """
     doc = {}
     if rights.status == rights_manager.ACTIVITY_STATUS_PUBLICIZED:
         doc['is'] = 'featured'
@@ -1150,17 +1516,41 @@ def _exp_rights_to_search_dict(rights):
 
 
 def _should_index(exp):
+    """Returns whether the given exploration should be indexed for future
+    search queries.
+
+    Args:
+        exp: Exploration domain object.
+    """
     rights = rights_manager.get_exploration_rights(exp.id)
     return rights.status != rights_manager.ACTIVITY_STATUS_PRIVATE
 
 
 def get_number_of_ratings(ratings):
+    """Gets the total number of ratings represented by the given ratings
+    object.
+
+    Args:
+        ratings: dict. A dict whose keys are '1', '2', '3', '4', '5' and whose
+            values are nonnegative integers representing frequency counts.
+
+    Returns:
+        int. The total number of ratings given.
+    """
     return sum(ratings.values())
 
 
 def get_average_rating(ratings):
-    """Returns the average rating of the ratings as a float. If there are no
-    ratings, it will return 0.
+    """Returns the average rating of the ratings as a float.
+    If there are no ratings, it will return 0.
+
+    Args:
+        ratings: dict. A dict whose keys are '1', '2', '3', '4', '5' and whose
+            values are nonnegative integers representing frequency counts.
+
+    Returns:
+        float. The average of the all the ratings given, or 0
+        if there are no rating.
     """
     rating_weightings = {'1': 1, '2': 2, '3': 3, '4': 4, '5': 5}
     if ratings:
@@ -1175,9 +1565,15 @@ def get_average_rating(ratings):
 
 
 def get_scaled_average_rating(ratings):
-    """Returns the lower bound wilson score of the ratings as a float. If
-    there are no ratings, it will return 0. The confidence of this result is
-    95%.
+    """Returns the lower bound wilson score of the ratings. If there are
+    no ratings, it will return 0. The confidence of this result is 95%.
+
+    Args:
+        ratings: dict. A dict whose keys are '1', '2', '3', '4', '5' and whose
+            values are nonnegative integers representing frequency counts.
+
+    Returns:
+        float. The lower bound wilson score of the ratings.
     """
     # The following is the number of ratings.
     n = get_number_of_ratings(ratings)
@@ -1200,6 +1596,12 @@ def get_search_rank_from_exp_summary(exp_summary):
     Featured explorations get a ranking bump, and so do explorations that
     have been more recently updated. Good ratings will increase the ranking
     and bad ones will lower it.
+
+    Args:
+        exp_summary: ExplorationSummary. ExplorationSummary domain object.
+
+    Returns:
+        int. Document's rank in search.
     """
     # TODO(sll): Improve this calculation.
     rating_weightings = {'1': -5, '2': -2, '3': 2, '4': 5, '5': 10}
@@ -1220,11 +1622,29 @@ def get_search_rank_from_exp_summary(exp_summary):
 
 
 def get_search_rank(exp_id):
+    """Returns the search rank.
+
+    Args:
+        exp_id: str. The id of the exploration.
+
+    Returns:
+        int. The rank of the exploration.
+    """
     exp_summary = get_exploration_summary_by_id(exp_id)
     return get_search_rank_from_exp_summary(exp_summary)
 
 
 def _exp_to_search_dict(exp):
+    """Updates the dict to be returned, whether the given exploration is to
+    be indexed for further queries or not.
+
+    Args:
+        exp: Exploration. Exploration domain object.
+
+    Returns:
+        dict. The representation of the given exploration, in a form that can
+        be used by the search index.
+    """
     rights = rights_manager.get_exploration_rights(exp.id)
     doc = {
         'id': exp.id,
@@ -1249,6 +1669,11 @@ def clear_search_index():
 
 
 def index_explorations_given_ids(exp_ids):
+    """Indexes the explorations corresponding to the given exploration ids.
+
+    Args:
+        exp_ids: list(str). List of ids of the explorations to be indexed.
+    """
     # We pass 'strict=False' so as not to index deleted explorations.
     exploration_models = get_multiple_explorations_by_id(exp_ids, strict=False)
     search_services.add_documents_to_index([
@@ -1260,6 +1685,11 @@ def index_explorations_given_ids(exp_ids):
 def patch_exploration_search_document(exp_id, update):
     """Patches an exploration's current search document, with the values
     from the 'update' dictionary.
+
+    Args:
+        exp_id: str. The id of the exploration to be patched.
+        update: dict. Key-value pairs to patch the exploration's search
+            document with.
     """
     doc = search_services.get_document_from_index(
         exp_id, SEARCH_INDEX_EXPLORATIONS)
@@ -1268,6 +1698,12 @@ def patch_exploration_search_document(exp_id, update):
 
 
 def update_exploration_status_in_search(exp_id):
+    """Updates the exploration status in its search doc.
+
+    Args:
+        exp_id: str. The id of the exploration whose status is to be
+            updated.
+    """
     rights = rights_manager.get_exploration_rights(exp_id)
     if rights.status == rights_manager.ACTIVITY_STATUS_PRIVATE:
         delete_documents_from_search_index([exp_id])
@@ -1277,6 +1713,13 @@ def update_exploration_status_in_search(exp_id):
 
 
 def delete_documents_from_search_index(exploration_ids):
+    """Deletes the documents corresponding to these exploration_ids from the
+    search index.
+
+    Args:
+        exploration_ids: list(str). A list of exploration ids whose
+            documents are to be deleted from the search index.
+    """
     search_services.delete_documents_from_index(
         exploration_ids, SEARCH_INDEX_EXPLORATIONS)
 
@@ -1284,24 +1727,25 @@ def delete_documents_from_search_index(exploration_ids):
 def search_explorations(query, limit, sort=None, cursor=None):
     """Searches through the available explorations.
 
-    args:
-      - query_string: the query string to search for.
-      - sort: a string indicating how to sort results. This should be a string
-          of space separated values. Each value should start with a '+' or a
-          '-' character indicating whether to sort in ascending or descending
-          order respectively. This character should be followed by a field name
-          to sort on. When this is None, results are based on 'rank'. See
-          get_search_rank to see how rank is determined.
-      - limit: the maximum number of results to return.
-      - cursor: A cursor, used to get the next page of results.
-          If there are more documents that match the query than 'limit', this
-          function will return a cursor to get the next page.
-
-    returns: a 2-tuple consisting of:
-      - a list of exploration ids that match the query.
-      - a cursor if there are more matching explorations to fetch, None
-          otherwise. If a cursor is returned, it will be a web-safe string that
-          can be used in URLs.
+    Args:
+        query_string: str. The query string to search for.
+        limit: int. The maximum number of results to return.
+        sort: str. A string indicating how to sort results. This should be a
+            string of space separated values. Each value should start with a
+            '+' or a '-' character indicating whether to sort in ascending or
+            descending order respectively. This character should be followed by
+            a field name to sort on. When this is None, results are based on
+            'rank'. See get_search_rank to see how rank is determined.
+        cursor: str or None. A cursor, used to get the next page of results. If
+            there are more documents that match the query than 'limit', this
+            function will return a cursor to get the next page.
+
+    Returns:
+        tuple. A 2-tuple consisting of:
+            - list(str). A list of exploration ids that match the query.
+            - str or None. A cursor if there are more matching explorations to
+              fetch, None otherwise. If a cursor is returned, it will be a
+              web-safe string that can be used in URLs.
     """
     return search_services.search(
         query, SEARCH_INDEX_EXPLORATIONS, cursor, limit, sort, ids_only=True)
@@ -1310,7 +1754,16 @@ def search_explorations(query, limit, sort=None, cursor=None):
 def _is_suggestion_valid(thread_id, exploration_id):
     """Check if the suggestion is still valid. A suggestion is considered
     invalid if the name of the state that the suggestion was made for has
-    changed since."""
+    changed since.
+
+    Args:
+        thread_id: str. Thread id of the feedback thread containing the
+            suggestion.
+        exploration_id: str. The id of the exploration.
+
+    Returns:
+        bool. Whether the suggestion is still valid.
+    """
 
     states = get_exploration_by_id(exploration_id).states
     suggestion = (
@@ -1320,7 +1773,17 @@ def _is_suggestion_valid(thread_id, exploration_id):
 
 
 def _is_suggestion_handled(thread_id, exploration_id):
-    """Checks if the current suggestion has already been accepted/rejected."""
+    """Checks if the current suggestion has already been accepted/rejected.
+
+    Args:
+        thread_id: str. Thread id of the feedback thread containing the
+            suggestion.
+        exploration_id: str. The id of the exploration.
+
+    Returns:
+        bool. Whether the current suggestion has already been acted upon (i.e.,
+        accepted or rejected).
+    """
 
     thread = feedback_models.FeedbackThreadModel.get_by_exp_and_thread_id(
         exploration_id, thread_id)
@@ -1331,7 +1794,23 @@ def _is_suggestion_handled(thread_id, exploration_id):
 
 
 def _create_change_list_from_suggestion(suggestion):
-    """Creates a change list from a suggestion object."""
+    """Creates a change list from a suggestion object.
+
+    Args:
+        suggestion: Suggestion. The given Suggestion domain object.
+
+    Returns:
+        list(dict). A dict containing a single change that represents an edit to
+        the state's content. The dict contains value and key (str.) pairs as
+        follows:
+            cmd: list(dict). The changelist corresponding to the given
+                suggestion domain object.
+            state_name: str or None. The state name for the thread. If None,
+                this indicates that the thread pertains to the exploration as a
+                whole.
+            new_value: list(str). List of the state content of the suggestion
+                object.
+    """
 
     return [{'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
              'state_name': suggestion.state_name,
@@ -1346,6 +1825,15 @@ def _get_commit_message_for_suggestion(
     NOTE TO DEVELOPERS: This should not be changed, since in the future we may
     want to determine and credit the original authors of suggestions, and in
     order to do so we will look for commit messages that follow this format.
+
+    Args:
+        suggestion_author_username: str. Username of the suggestion author.
+        commit_message: str. The original commit message submitted by the
+            suggestion author.
+
+    Returns:
+        str. The modified commit message to be used in the exploration commit
+        logs.
     """
     return '%s %s: %s' % (
         feconf.COMMIT_MESSAGE_ACCEPTED_SUGGESTION_PREFIX,
@@ -1354,7 +1842,20 @@ def _get_commit_message_for_suggestion(
 
 def accept_suggestion(editor_id, thread_id, exploration_id, commit_message):
     """If the suggestion is valid, accepts it by updating the exploration.
-    Raises an exception if the suggestion is not valid."""
+    Raises an exception if the suggestion is not valid.
+
+    Args:
+        editor_id: str. The user id of the editor.
+        thread_id: str. The id of the suggestion thread.
+        exploration_id: str. The id of the exploration that the suggestion is
+            for.
+        commit_message: str. The commit message.
+
+    Raises:
+        Exception: The suggestion is not valid.
+        Exception: The commit message is empty.
+        Exception: The suggestion has already been accepted or rejected.
+    """
 
     if not commit_message or not commit_message.strip():
         raise Exception('Commit message cannot be empty.')
@@ -1380,8 +1881,17 @@ def accept_suggestion(editor_id, thread_id, exploration_id, commit_message):
 
 
 def reject_suggestion(editor_id, thread_id, exploration_id):
-    """Set the state of a suggetion to REJECTED."""
+    """Set the status of a suggestion to REJECTED.
 
+    Args:
+        editor_id: str. User id of the editor.
+        thread_id: str. The id of the suggestion thread.
+        exploration_id: str. The id of the exploration that the suggestion is
+            for.
+
+    Raises:
+        Exception: The suggestion has already been accepted or rejected.
+    """
     if _is_suggestion_handled(thread_id, exploration_id):
         raise Exception('Suggestion has already been accepted/rejected.')
     else:
@@ -1396,7 +1906,16 @@ def reject_suggestion(editor_id, thread_id, exploration_id):
 
 def is_version_of_draft_valid(exp_id, version):
     """Checks if the draft version is the same as the latest version of the
-    exploration."""
+    exploration.
+
+    Args:
+        exp_id: str. The id of the exploration.
+        version: int. The draft version which is to be validate.
+
+    Returns:
+        bool. Whether the given version number is the same as the current
+        version number of the exploration in the datastore.
+    """
 
     return get_exploration_by_id(exp_id).version == version
 
@@ -1408,7 +1927,16 @@ def create_or_update_draft(
     list timestamp of the new change list is greater than the change list
     timestamp of the draft.
     The method assumes that a ExplorationUserDataModel object exists for the
-    given user and exploration."""
+    given user and exploration.
+
+    Args:
+        exp_id: str. The id of the exploration.
+        user_id: str. The id of the user.
+        change_list: list(str). A list that contains the changes to be made to
+            the ExplorationUserDataModel object.
+        exp_version: int. The current version of the exploration.
+        current_datetime: datetime.datetime. The current date and time.
+    """
     exp_user_data = user_models.ExplorationUserDataModel.get(user_id, exp_id)
     if (exp_user_data and exp_user_data.draft_change_list and
             exp_user_data.draft_change_list_last_updated > current_datetime):
@@ -1429,7 +1957,12 @@ def create_or_update_draft(
 
 def get_exp_with_draft_applied(exp_id, user_id):
     """If a draft exists for the given user and exploration,
-    apply it to the exploration."""
+    apply it to the exploration.
+
+    Args:
+        exp_id: str. The id of the exploration.
+        user_id: str. The id of the user whose draft is to be applied.
+    """
 
     exp_user_data = user_models.ExplorationUserDataModel.get(user_id, exp_id)
     exploration = get_exploration_by_id(exp_id)
@@ -1442,7 +1975,12 @@ def get_exp_with_draft_applied(exp_id, user_id):
 
 
 def discard_draft(exp_id, user_id):
-    """Discard the draft for the given user and exploration."""
+    """Discard the draft for the given user and exploration.
+
+    Args:
+        exp_id: str. The id of the exploration.
+        user_id: str. The id of the user whose draft is to be discarded.
+    """
 
     exp_user_data = user_models.ExplorationUserDataModel.get(
         user_id, exp_id)


Test patch: