{"action":"created","comment":{"url":"https://api.github.com/repos/spencerahill/aospy/pulls/comments/82693612","pull_request_review_id":3583547,"id":82693612,"diff_hunk":"@@ -0,0 +1,168 @@\n+\"\"\"asopy DataLoader objects\"\"\"\n+import xarray as xr\n+\n+from .__config__ import (TIME_STR, GRID_ATTRS, TIME_BOUNDS_STR)\n+from .timedate import TimeManager\n+\n+\n+class DataLoader(object):\n+    \"\"\"A fundamental DataLoader object\"\"\"\n+    def __init__(self):\n+        self.correction = None\n+\n+    def load_variable(self, var=None, start_date=None, end_date=None,\n+                      intvl_in=None):\n+        \"\"\"Returns the DataArray with requested variable, given time range,\n+        and input interval.\n+\n+        Automatically renames all grid attributes to match aospy conventions.\n+\n+        Parameters\n+        ----------\n+        var : Var\n+             aospy Var object\n+        start_date : netCDF4.netcdftime or np.datetime64\n+             start date for interval\n+        end_date : netCDF4.netcdftime or np.datetime64\n+             end date for interval\n+        intvl_in : str\n+             interval coding (e.g. 'monthly')\n+\n+        Returns\n+        -------\n+        da : DataArray\n+             DataArray for the specified variable, date range, and interval in\n+        \"\"\"\n+        file_set = self._generate_file_set(var, start_date, end_date, intvl_in)\n+        ds = xr.open_mfdataset(file_set, preprocess=self.rename_grid_attrs,\n+                               concat_dim=TIME_STR, decode_cf=False)\n+        ds = TimeManager.enforce_valid_timestamp_date_range(ds)  # Tested\n+        if TIME_BOUNDS_STR in ds:\n+            ds = TimeManager.set_average_dt_metadata(ds)  # Tested\n+        ds = self.set_grid_attrs_as_coords(ds)  # Tested\n+        ds = xr.decode_cf(ds)\n+        da = self._sel_var(ds, var)  # Tested\n+\n+        # Apply correction before selecting time range.\n+        if self.correction:\n+            da = self.correction(da)\n+\n+        return TimeManager.sel_time(da, start_date, end_date).load()\n+\n+    @staticmethod\n+    def _sel_var(ds, var):\n+        \"\"\"Searches a Dataset for the specified variable trying all possible\n+        alternative names.\n+\n+        Parameters\n+        ----------\n+        ds : Dataset\n+            Dataset possibly containing var\n+        var : aospy.Var\n+            Variable to find data for\n+\n+        Returns\n+        -------\n+        DataArray\n+\n+        Raises\n+        ------\n+        KeyError\n+             If the variable is not in the Dataset\n+        \"\"\"\n+        for name in var.names:\n+            try:\n+                da = ds[name]\n+                return da.rename({name: var.name})\n+            except KeyError:\n+                pass\n+        raise KeyError('{0} not found in '\n+                       'among names:{1} in {2}'.format(var, var.names, ds))\n+\n+    @staticmethod\n+    def rename_grid_attrs(ds):\n+        \"\"\"Renames existing grid attributes to be consistent with\n+        aospy conventions.  Does not compare to Model coordinates or\n+        add missing coordinates from Model objects.  Grid attributes\n+        are set as coordinates, such that they are carried by all\n+        selected DataArrays with overlapping index dimensions.\n+\n+        Parameters\n+        ----------\n+        ds : Dataset\n+\n+        Returns\n+        -------\n+        renamed : Dataset\n+            Dataset returned with coordinates consistent with aospy\n+            conventions\n+        \"\"\"\n+        # TODO: Separate coord setting and coord renaming.  Coord renaming\n+        # should come before coord setting.  Set coords just before you select\n+        # the variable.\n+        for name_int, names_ext in GRID_ATTRS.items():\n+            ds_coord_name = set(names_ext).intersection(set(ds.coords) |\n+                                                        set(ds.data_vars))\n+            if ds_coord_name:\n+                ds.rename({ds_coord_name.pop(): name_int}, inplace=True)\n+                # ds.set_coords(name_int, inplace=True)\n+        return ds\n+\n+    @staticmethod\n+    def set_grid_attrs_as_coords(ds):\n+        \"\"\"Sets available grid attributes as coordinates in a given Dataset.\n+        Grid attributes are assumed to have their internal aospy names.\n+\n+        Parameters\n+        ----------\n+        ds : Dataset\n+            Input data\n+\n+        Returns\n+        -------\n+        Dataset\n+            Dataset with grid attributes set as coordinates\n+        \"\"\"\n+        int_names = GRID_ATTRS.keys()\n+        grid_attrs_in_ds = set(int_names).intersection(set(ds.coords) |\n+                                                       set(ds.data_vars))\n+        ds.set_coords(grid_attrs_in_ds, inplace=True)\n+        return ds\n+\n+    def _generate_file_set(self, var, start_date, end_date, intvl_in):\n+        raise NotImplementedError(\n+            'All DataLoaders require a _generate_file_set method')\n+\n+\n+class DictDataLoader(DataLoader):\n+    \"\"\"A data loader that corresponds with a dictionary mapping lists of files\n+    to interval in tags.\n+    \"\"\"\n+    def __init__(self, file_map={}, correction=None):\n+        \"\"\"Instantiates a new `DictDataLoader`\n+\n+        Parameters\n+        ----------\n+        file_map : dict\n+            A dict mapping an input interval to a list of files\n+        correction : function\n+            An optional function to correct an aspect of input data before\n+            subsetting in time\n+        \"\"\"\n+        self.file_map = file_map\n+        self.correction = correction\n+\n+    def _generate_file_set(self, var, start_date, end_date, intvl_in):","path":"aospy/data_loader.py","position":155,"original_position":155,"commit_id":"42b63ba84b239e41af65f63aedb6c863d14bdef5","original_commit_id":"42b63ba84b239e41af65f63aedb6c863d14bdef5","user":{"login":"spencerahill","id":6200806,"avatar_url":"https://avatars.githubusercontent.com/u/6200806?v=3","gravatar_id":"","url":"https://api.github.com/users/spencerahill","html_url":"https://github.com/spencerahill","followers_url":"https://api.github.com/users/spencerahill/followers","following_url":"https://api.github.com/users/spencerahill/following{/other_user}","gists_url":"https://api.github.com/users/spencerahill/gists{/gist_id}","starred_url":"https://api.github.com/users/spencerahill/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spencerahill/subscriptions","organizations_url":"https://api.github.com/users/spencerahill/orgs","repos_url":"https://api.github.com/users/spencerahill/repos","events_url":"https://api.github.com/users/spencerahill/events{/privacy}","received_events_url":"https://api.github.com/users/spencerahill/received_events","type":"User","site_admin":false},"body":"Seems a bit clunky to have start_date and end_date as required but unused args that go before intvl_in.  Is there potentially a better call signature?  Tricky because the requirements/logic will vary across DataLoader implementations","created_at":"2016-10-10T22:18:59Z","updated_at":"2016-10-10T22:19:06Z","html_url":"https://github.com/spencerahill/aospy/pull/90#discussion_r82693612","pull_request_url":"https://api.github.com/repos/spencerahill/aospy/pulls/90","_links":{"self":{"href":"https://api.github.com/repos/spencerahill/aospy/pulls/comments/82693612"},"html":{"href":"https://github.com/spencerahill/aospy/pull/90#discussion_r82693612"},"pull_request":{"href":"https://api.github.com/repos/spencerahill/aospy/pulls/90"}}},"pull_request":{"url":"https://api.github.com/repos/spencerahill/aospy/pulls/90","id":87029097,"html_url":"https://github.com/spencerahill/aospy/pull/90","diff_url":"https://github.com/spencerahill/aospy/pull/90.diff","patch_url":"https://github.com/spencerahill/aospy/pull/90.patch","issue_url":"https://api.github.com/repos/spencerahill/aospy/issues/90","number":90,"state":"open","locked":false,"title":"WIP: Refactor variable-loading portion of calc.py","user":{"login":"spencerkclark","id":6628425,"avatar_url":"https://avatars.githubusercontent.com/u/6628425?v=3","gravatar_id":"","url":"https://api.github.com/users/spencerkclark","html_url":"https://github.com/spencerkclark","followers_url":"https://api.github.com/users/spencerkclark/followers","following_url":"https://api.github.com/users/spencerkclark/following{/other_user}","gists_url":"https://api.github.com/users/spencerkclark/gists{/gist_id}","starred_url":"https://api.github.com/users/spencerkclark/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spencerkclark/subscriptions","organizations_url":"https://api.github.com/users/spencerkclark/orgs","repos_url":"https://api.github.com/users/spencerkclark/repos","events_url":"https://api.github.com/users/spencerkclark/events{/privacy}","received_events_url":"https://api.github.com/users/spencerkclark/received_events","type":"User","site_admin":false},"body":"This PR is a start on addressing a number of issues (most notably #32 and #33).  This is very much a work in progress, but I wanted to get some discussion started on this topic.  @spencerahill when you get a chance, please have a look!\r\n\r\nAt the core of this PR is a new ```DataLoader``` base class, which implements some methods (with unit tests) meant to streamline and improve upon the current setup in in [Calc._create_input_data_obj](https://github.com/spencerahill/aospy/blob/develop/aospy/calc.py#L442) and [Calc._add_grid_attributes](https://github.com/spencerahill/aospy/blob/develop/aospy/calc.py#L376).   The ```DataLoader.load_variable``` method is what I envision would be called in ```calc.py``` directly (and replace a lot of existing code).  It takes the requested variable, start date, end date, and interval in as arguments, and returns a DataArray with the specified variable, subset for the requested time range, with all local grid attributes renamed to match ```aospy``` internal conventions.\r\n\r\nI'll emphasize here that currently, both ```Calc._add_grid_attributes``` and ```Calc._create_input_data_obj```  do a lot:\r\n\r\n- ```Calc._add_grid_attributes``` not only renames local grid attributes, but also assimilates ```Model``` grid attributes into the existing Dataset. \r\n- ```Calc._create_input_data_obj``` also computes a ```dt``` and ```pfull``` variable if required and corrects out of range dates if needed. \r\n\r\nI currently do not implement any of those things in ```DataLoader```.  The only one I think that would make sense to include eventually would be the date range adjustment step (it would be part of the ```preprocess``` method called by ```xr.open_mfdataset``` in ```DataLoader._sel_var```).  Assimilating ```Model``` grid attributes and computing pressures and timedeltas can be done elsewhere if needed.\r\n\r\nMethods called by ```DataLoader.load_variable``` are meant to improve upon the current setup.  To start, there is no need to manually concatenate the files we read in; in fact we should leverage ```xr.open_mfdataset```'s lazy-loading functionality to prevent unnecessarily loading in datapoints from un-needed date ranges and un-needed variables.  This is done in the ```DataLoader._sel_var``` method.  It loads a set of files, preprocesses them to rename the grid attributes, and then searches the Dataset for the requested variable, without loading anything into memory.  The ```DataLoader._sel_time``` method then first checks to see if data for the requested variable exist for the full time range requested (addressing #72), before returning the sliced DataArray.  Finally, before returning, the ```DataLoader.load_variable``` method explicitly \"loads\" the data into memory (i.e. converts the underlying array from a dask array to a numpy array) to prevent previously-encountered chunking-related performance issues.\r\n\r\nHere, an extremely basic ```DataLoader``` is implemented for testing purposes.  It is called a ```DictDataLoader```, because it simply takes a ```dict``` as the sole argument in its constructor, which maps a string ```intvl_in``` to a list of filepaths.  It is assumed that all variables the user may request can be found in those files.  An example can be found in the unit tests of the PR. \r\n\r\nI have not yet ported the current methods (e.g. ```'gfdl'```, ```'one_dir'``` etc.) to this paradigm yet.  That being said, the way things are designed here, it would just require some new classes, extending the base ```DataLoader``` class, each implementing their own ```DataLoader._generate_file_set``` method (which generates a list of filepaths containing data for a given variable, time range, and input interval type) consistent with how they currently operate.\r\n\r\nPS: Apologies for the odd commit history here -- the diff reflects only the changes associated with this PR (i.e. the commit on Sept. 27).  If we eventually elect to merge a later version of this, I'll try and clean things up.","created_at":"2016-09-27T23:11:51Z","updated_at":"2016-10-10T22:19:06Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7cd2285d16adb5af47c7a7878271a60cab4a944d","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/spencerahill/aospy/pulls/90/commits","review_comments_url":"https://api.github.com/repos/spencerahill/aospy/pulls/90/comments","review_comment_url":"https://api.github.com/repos/spencerahill/aospy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/spencerahill/aospy/issues/90/comments","statuses_url":"https://api.github.com/repos/spencerahill/aospy/statuses/42b63ba84b239e41af65f63aedb6c863d14bdef5","head":{"label":"spencerkclark:data-loader","ref":"data-loader","sha":"42b63ba84b239e41af65f63aedb6c863d14bdef5","user":{"login":"spencerkclark","id":6628425,"avatar_url":"https://avatars.githubusercontent.com/u/6628425?v=3","gravatar_id":"","url":"https://api.github.com/users/spencerkclark","html_url":"https://github.com/spencerkclark","followers_url":"https://api.github.com/users/spencerkclark/followers","following_url":"https://api.github.com/users/spencerkclark/following{/other_user}","gists_url":"https://api.github.com/users/spencerkclark/gists{/gist_id}","starred_url":"https://api.github.com/users/spencerkclark/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spencerkclark/subscriptions","organizations_url":"https://api.github.com/users/spencerkclark/orgs","repos_url":"https://api.github.com/users/spencerkclark/repos","events_url":"https://api.github.com/users/spencerkclark/events{/privacy}","received_events_url":"https://api.github.com/users/spencerkclark/received_events","type":"User","site_admin":false},"repo":{"id":41910093,"name":"aospy","full_name":"spencerkclark/aospy","owner":{"login":"spencerkclark","id":6628425,"avatar_url":"https://avatars.githubusercontent.com/u/6628425?v=3","gravatar_id":"","url":"https://api.github.com/users/spencerkclark","html_url":"https://github.com/spencerkclark","followers_url":"https://api.github.com/users/spencerkclark/followers","following_url":"https://api.github.com/users/spencerkclark/following{/other_user}","gists_url":"https://api.github.com/users/spencerkclark/gists{/gist_id}","starred_url":"https://api.github.com/users/spencerkclark/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spencerkclark/subscriptions","organizations_url":"https://api.github.com/users/spencerkclark/orgs","repos_url":"https://api.github.com/users/spencerkclark/repos","events_url":"https://api.github.com/users/spencerkclark/events{/privacy}","received_events_url":"https://api.github.com/users/spencerkclark/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/spencerkclark/aospy","description":"Climate data analysis, database, and visualization package for Python","fork":true,"url":"https://api.github.com/repos/spencerkclark/aospy","forks_url":"https://api.github.com/repos/spencerkclark/aospy/forks","keys_url":"https://api.github.com/repos/spencerkclark/aospy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/spencerkclark/aospy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/spencerkclark/aospy/teams","hooks_url":"https://api.github.com/repos/spencerkclark/aospy/hooks","issue_events_url":"https://api.github.com/repos/spencerkclark/aospy/issues/events{/number}","events_url":"https://api.github.com/repos/spencerkclark/aospy/events","assignees_url":"https://api.github.com/repos/spencerkclark/aospy/assignees{/user}","branches_url":"https://api.github.com/repos/spencerkclark/aospy/branches{/branch}","tags_url":"https://api.github.com/repos/spencerkclark/aospy/tags","blobs_url":"https://api.github.com/repos/spencerkclark/aospy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/spencerkclark/aospy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/spencerkclark/aospy/git/refs{/sha}","trees_url":"https://api.github.com/repos/spencerkclark/aospy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/spencerkclark/aospy/statuses/{sha}","languages_url":"https://api.github.com/repos/spencerkclark/aospy/languages","stargazers_url":"https://api.github.com/repos/spencerkclark/aospy/stargazers","contributors_url":"https://api.github.com/repos/spencerkclark/aospy/contributors","subscribers_url":"https://api.github.com/repos/spencerkclark/aospy/subscribers","subscription_url":"https://api.github.com/repos/spencerkclark/aospy/subscription","commits_url":"https://api.github.com/repos/spencerkclark/aospy/commits{/sha}","git_commits_url":"https://api.github.com/repos/spencerkclark/aospy/git/commits{/sha}","comments_url":"https://api.github.com/repos/spencerkclark/aospy/comments{/number}","issue_comment_url":"https://api.github.com/repos/spencerkclark/aospy/issues/comments{/number}","contents_url":"https://api.github.com/repos/spencerkclark/aospy/contents/{+path}","compare_url":"https://api.github.com/repos/spencerkclark/aospy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/spencerkclark/aospy/merges","archive_url":"https://api.github.com/repos/spencerkclark/aospy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/spencerkclark/aospy/downloads","issues_url":"https://api.github.com/repos/spencerkclark/aospy/issues{/number}","pulls_url":"https://api.github.com/repos/spencerkclark/aospy/pulls{/number}","milestones_url":"https://api.github.com/repos/spencerkclark/aospy/milestones{/number}","notifications_url":"https://api.github.com/repos/spencerkclark/aospy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/spencerkclark/aospy/labels{/name}","releases_url":"https://api.github.com/repos/spencerkclark/aospy/releases{/id}","deployments_url":"https://api.github.com/repos/spencerkclark/aospy/deployments","created_at":"2015-09-04T10:25:29Z","updated_at":"2015-09-04T10:25:29Z","pushed_at":"2016-10-08T15:17:59Z","git_url":"git://github.com/spencerkclark/aospy.git","ssh_url":"git@github.com:spencerkclark/aospy.git","clone_url":"https://github.com/spencerkclark/aospy.git","svn_url":"https://github.com/spencerkclark/aospy","homepage":null,"size":664,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"spencerahill:develop","ref":"develop","sha":"68add22f60cb60258fba3a95495973f68b955f23","user":{"login":"spencerahill","id":6200806,"avatar_url":"https://avatars.githubusercontent.com/u/6200806?v=3","gravatar_id":"","url":"https://api.github.com/users/spencerahill","html_url":"https://github.com/spencerahill","followers_url":"https://api.github.com/users/spencerahill/followers","following_url":"https://api.github.com/users/spencerahill/following{/other_user}","gists_url":"https://api.github.com/users/spencerahill/gists{/gist_id}","starred_url":"https://api.github.com/users/spencerahill/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spencerahill/subscriptions","organizations_url":"https://api.github.com/users/spencerahill/orgs","repos_url":"https://api.github.com/users/spencerahill/repos","events_url":"https://api.github.com/users/spencerahill/events{/privacy}","received_events_url":"https://api.github.com/users/spencerahill/received_events","type":"User","site_admin":false},"repo":{"id":37231620,"name":"aospy","full_name":"spencerahill/aospy","owner":{"login":"spencerahill","id":6200806,"avatar_url":"https://avatars.githubusercontent.com/u/6200806?v=3","gravatar_id":"","url":"https://api.github.com/users/spencerahill","html_url":"https://github.com/spencerahill","followers_url":"https://api.github.com/users/spencerahill/followers","following_url":"https://api.github.com/users/spencerahill/following{/other_user}","gists_url":"https://api.github.com/users/spencerahill/gists{/gist_id}","starred_url":"https://api.github.com/users/spencerahill/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spencerahill/subscriptions","organizations_url":"https://api.github.com/users/spencerahill/orgs","repos_url":"https://api.github.com/users/spencerahill/repos","events_url":"https://api.github.com/users/spencerahill/events{/privacy}","received_events_url":"https://api.github.com/users/spencerahill/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/spencerahill/aospy","description":"Climate data analysis, database, and visualization package for Python","fork":false,"url":"https://api.github.com/repos/spencerahill/aospy","forks_url":"https://api.github.com/repos/spencerahill/aospy/forks","keys_url":"https://api.github.com/repos/spencerahill/aospy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/spencerahill/aospy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/spencerahill/aospy/teams","hooks_url":"https://api.github.com/repos/spencerahill/aospy/hooks","issue_events_url":"https://api.github.com/repos/spencerahill/aospy/issues/events{/number}","events_url":"https://api.github.com/repos/spencerahill/aospy/events","assignees_url":"https://api.github.com/repos/spencerahill/aospy/assignees{/user}","branches_url":"https://api.github.com/repos/spencerahill/aospy/branches{/branch}","tags_url":"https://api.github.com/repos/spencerahill/aospy/tags","blobs_url":"https://api.github.com/repos/spencerahill/aospy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/spencerahill/aospy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/spencerahill/aospy/git/refs{/sha}","trees_url":"https://api.github.com/repos/spencerahill/aospy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/spencerahill/aospy/statuses/{sha}","languages_url":"https://api.github.com/repos/spencerahill/aospy/languages","stargazers_url":"https://api.github.com/repos/spencerahill/aospy/stargazers","contributors_url":"https://api.github.com/repos/spencerahill/aospy/contributors","subscribers_url":"https://api.github.com/repos/spencerahill/aospy/subscribers","subscription_url":"https://api.github.com/repos/spencerahill/aospy/subscription","commits_url":"https://api.github.com/repos/spencerahill/aospy/commits{/sha}","git_commits_url":"https://api.github.com/repos/spencerahill/aospy/git/commits{/sha}","comments_url":"https://api.github.com/repos/spencerahill/aospy/comments{/number}","issue_comment_url":"https://api.github.com/repos/spencerahill/aospy/issues/comments{/number}","contents_url":"https://api.github.com/repos/spencerahill/aospy/contents/{+path}","compare_url":"https://api.github.com/repos/spencerahill/aospy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/spencerahill/aospy/merges","archive_url":"https://api.github.com/repos/spencerahill/aospy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/spencerahill/aospy/downloads","issues_url":"https://api.github.com/repos/spencerahill/aospy/issues{/number}","pulls_url":"https://api.github.com/repos/spencerahill/aospy/pulls{/number}","milestones_url":"https://api.github.com/repos/spencerahill/aospy/milestones{/number}","notifications_url":"https://api.github.com/repos/spencerahill/aospy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/spencerahill/aospy/labels{/name}","releases_url":"https://api.github.com/repos/spencerahill/aospy/releases{/id}","deployments_url":"https://api.github.com/repos/spencerahill/aospy/deployments","created_at":"2015-06-11T01:12:43Z","updated_at":"2016-10-01T09:38:42Z","pushed_at":"2016-10-08T15:18:01Z","git_url":"git://github.com/spencerahill/aospy.git","ssh_url":"git@github.com:spencerahill/aospy.git","clone_url":"https://github.com/spencerahill/aospy.git","svn_url":"https://github.com/spencerahill/aospy","homepage":null,"size":652,"stargazers_count":12,"watchers_count":12,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":44,"forks":1,"open_issues":44,"watchers":12,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/spencerahill/aospy/pulls/90"},"html":{"href":"https://github.com/spencerahill/aospy/pull/90"},"issue":{"href":"https://api.github.com/repos/spencerahill/aospy/issues/90"},"comments":{"href":"https://api.github.com/repos/spencerahill/aospy/issues/90/comments"},"review_comments":{"href":"https://api.github.com/repos/spencerahill/aospy/pulls/90/comments"},"review_comment":{"href":"https://api.github.com/repos/spencerahill/aospy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/spencerahill/aospy/pulls/90/commits"},"statuses":{"href":"https://api.github.com/repos/spencerahill/aospy/statuses/42b63ba84b239e41af65f63aedb6c863d14bdef5"}}}}
{"actor":{"display_login":"spencerahill"}}