{"action":"created","comment":{"url":"https://api.github.com/repos/numfocus/gsoc/pulls/comments/108533476","pull_request_review_id":29566250,"id":108533476,"diff_hunk":"@@ -0,0 +1,628 @@\n+Port testsuite to pytest\n+========================\n+\n+\n+Abstract\n+========\n+Testing is crucial to the development of any software and MDAnalysis currently uses nose to test their code. \n+Unfortunately, [nose](http://nose.readthedocs.io/en/latest/) is no longer under active development so the community has decided to shift over to [pytest](http://doc.pytest.org/en/latest/).\n+The objective of this project is to implement this shift in a way that existing development work is not affected\n+and to standardize and improve the existing test cases.\n+\n+\n+Current State\n+=============\n+Currently the testsuite consists of 8 modules that have a total of 4731 test cases. Running this testsuite with \n+pytest (without any modifications) results in the following output:\n+\n+`1789 failed, 2630 passed, 52 skipped, 1610 pytest-warnings, 26 error`\n+\n+Also, it is to be noted that pytest misses out some (231 to be exact) test cases too. This is due to differences \n+between test discovery rules of nose and pytest.\n+\n+\n+Technical Details\n+=================\n+\n+Incompatible nose idioms\n+------------------------\n+\n+1. Test classes with `__init__` methods are not collected by pytest.\n+\n+    **Example**\n+\n+    ```python\n+    class TestGROReader(BaseReaderTest):\n+      def __init__(self, reference=None):\n+        if reference is None:\n+        reference = GROReference()\n+        super(TestGROReader, self).__init__(reference)\n+\n+      def test_flag_convert_lengths(self):\n+        assert_equal(mda.core.flags['convert_lengths'], True,\n+        \"MDAnalysis.core.flags['convert_lengths'] should be True \"\n+        \"by default\")\n+\n+      def test_time(self):\n+        u = mda.Universe(self.ref.topology, self.ref.trajectory)\n+        assert_equal(u.trajectory.time, 0.0,\n+        \"wrong time of the frame\")\n+\n+      def test_full_slice(self):\n+        u = mda.Universe(self.ref.topology, self.ref.trajectory)\n+        trj_iter = u.trajectory[:]\n+        frames = [ts.frame for ts in trj_iter]\n+        assert_equal(frames, np.arange(u.trajectory.n_frames))\n+    ```\n+\n+\n+    This class is not collected for tests because `__init__` is present\n+\n+\n+2. `setUp` and `tearDown` methods are not executed in test classes that do not inherit from `TestCase`.\n+\n+    **Example**\n+\n+    ```python\n+    class TestGROIncompleteVels(object):\n+      def setUp(self):\n+      \tself.u = mda.Universe(GRO_incomplete_vels)\n+\n+      def tearDown(self):\n+      \tdel self.u\n+\n+      def test_load(self):\n+      \tassert_equal(len(self.u.atoms), 4)\n+\n+      def test_velocities(self):\n+        assert_array_almost_equal(self.u.atoms[0].velocity,\n+        np.array([ 79.56,  124.08,   49.49]),\n+        decimal=3)\n+        assert_array_almost_equal(self.u.atoms[2].velocity,\n+        np.array([0.0, 0.0, 0.0]),\n+        decimal=3)\n+    ```\n+\n+    **Output**\n+    `AttributeError - 'TestGROIncompleteVels' object has no attribute 'u'`\n+\n+    **Solution**\n+    Inheriting from `TestCase` fixes this problem\n+\n+3. `yield` based test generators are deprecated in the current version of pytest. For now, they are executed just\n+fine, but a deprecation warning is displayed.\n+\n+\t**Example**\n+\n+    ```python\n+    def check_even(n, nn):\n+      print(n)\n+      assert n % 2 == 0 or nn % 2 == 0\n+\n+\n+    def test_evens():\n+      for i in range(0, 5):\n+      print(i)\n+      yield check_even, i, i * 3\n+\n+\t```\n+\n+    **Warning**\n+\n+    ```\n+    WC1 /Users/utkbansal/Code/mdanalysis/testsuite/MDAnalysisTests/coordinates/experiments.py yield tests are deprecated, and scheduled to be removed in pytest 4.0\n+    WC1 /Users/utkbansal/Code/mdanalysis/testsuite/MDAnalysisTests/coordinates/experiments.py yield tests are deprecated, and scheduled to be removed in pytest 4.0\n+    WC1 /Users/utkbansal/Code/mdanalysis/testsuite/MDAnalysisTests/coordinates/experiments.py yield tests are deprecated, and scheduled to be removed in pytest 4.0\n+    WC1 /Users/utkbansal/Code/mdanalysis/testsuite/MDAnalysisTests/coordinates/experiments.py yield tests are deprecated, and scheduled to be removed in pytest 4.0\n+    WC1 /Users/utkbansal/Code/mdanalysis/testsuite/MDAnalysisTests/coordinates/experiments.py yield tests are deprecated, and scheduled to be removed in pytest 4.0\n+    ```\n+\n+    **Solution**\n+    Using `pytest.mark.parametrize` is the recommended way to port yield based tests to pytest.\n+\n+    ```python\n+    @pytest.mark.parametrize(\"n, nn\", [\n+      (0, 0),\n+      pytest.mark.xfail((1, 3)),\n+      (2, 6),\n+    ])\n+    def check_even(n, nn):\n+      print(n)\n+      assert n % 2 == 0 or nn % 2 == 0\n+    ```\n+\n+\n+4. Test case discovery in pytest is quite different from that in nose.\n+\n+\t**Example**\n+\n+    ```python\n+    class _IgnoreClass(TestCase):\n+\n+      def test_number(self):\n+      \tassert 2 == 2\n+\n+      def test_bool(self):\n+      \tassert True is True\n+\n+      def dont_collect(self):\n+      \tassert 1 == 1\n+\n+\n+    class IgnoreClass(object):\n+      def test_number(self):\n+      \tassert 2 == 2\n+\n+      def test_bool(self):\n+      \tassert True is False\n+\t```\n+\n+  In the above case,  `_IgnoreClass` is collected even tough it doesn't follow the naming convention required for test discovery and the class `IgnoreClass` is not collected.\n+\n+5. Inheritance also causes problems in pytest.\n+\n+    **Example**\n+    ```python\n+    class ParentClass(TestCase):\n+\n+      def test_value(self):\n+      \tassert self.a == 2\n+\n+\n+    class TestChildClass(ParentClass):\n+    \ta = 2\n+    ```\n+    The problem with the above example is that both the test classes are collected for tests (only `TestChildClass` should be collected)\n+    and hence test case in `ParentClass` fails\n+\n+    **Solution**\n+    ```python\n+    class ParentClass(TestCase):\n+      __test__ = False\n+\n+      def test_value(self):\n+          assert self.a == 2\n+\n+\n+    class TestChildClass(ParentClass):\n+      __test__ = True\n+      a = 2\n+    ```\n+    Here only `TestChildClass` is collected for test cases.\n+\n+\n+Useful pytest features\n+----------------------\n+\n+1. Fixtures - pytest has completely different type of fixtures that are very modular and simple. The documentation\n+is available [here](http://doc.pytest.org/en/latest/fixture.html). Using fixtures is one of the major goals of this project as it will help in making the tests\n+more modular and might even help speed them up (by caching parsed files among others, see [#1191](https://github.com/MDAnalysis/mdanalysis/issues/1191)).\n+\n+    **Usage**\n+\n+    Currently we have a class `TempDir` (`MDAnalysisTests/tempdir.py`) that is used to get temporary directory for writing files. This is used as follows -\n+    ```python\n+    class BaseWriterTest(object):\n+      def __init__(self, reference):\n+        self.ref = reference\n+\n+        # Instantiating a TempDir object\n+        self.tmpdir = tempdir.TempDir()\n+        self.reader = self.ref.reader(self.ref.trajectory)\n+\n+      def tmp_file(self, name):\n+\n+        # Note the usage to self.tmpdir here\n+        return self.tmpdir.name + name + '.' + self.ref.ext\n+\n+    ```\n+\n+    Custom fixtures are easy to create. The tempdir instantiation part can be easily converted into a custom fixture as follows\n+    ```python\n+\n+    # Creating a custom fixture\n+    @pytest.fixture\n+    def tmpdir():\n+        return tempdir.TempDir()\n+\n+    class BaseWriterTest(object):\n+      def __init__(self, reference):\n+        self.ref = reference\n+        # Note that we do not have to initialize self.tmpdir how\n+        self.reader = self.ref.reader(self.ref.trajectory)\n+\n+      # Note that the method now takes an additional argument - tmpdir (our fixture)\n+      def tmp_file(self, name, tmpdir):\n+        # Note the usage to tmpdir here\n+        return tmpdir.name + name + '.' + self.ref.ext\n+    ```\n+\n+    Moreover fixtures have configurable scopes and can be used to cache resources that are reused, i.e. there is no need to initialize a particular resource everytime\n+    it is needed in a different class/function. This will lead to performance gains too and will help reduce boilerplate code further.\n+\n+    ```python\n+\n+    # Note the usage of scope\n+    @pytest.fixture(scope=\"function\")\n+    def custom_fixture():\n+      return resource\n+    ```\n+\n+    The scope can be \"function\", \"class\", \"module\", \"session\" or \"invocation\". This way we can cache resources that are being reused in other parts of the code.\n+    As with the above example, many parts of the code require the `tmpdir` and as of now each class instantiates its own object. The scope of this fixture may be\n+    changed to `\"session\"` so that the object is created only once.\n+\n+    Pytest also has some commonly used inbuilt fixtures.\n+    An interesting one of them is `cache` that returns a cache object that can persist state between testing sessions.\n+\n+    ```python\n+    cache.get(key, default)\n+    cache.set(key, value)\n+    ```\n+\n+    This fixture may be used to speed up tests and TravisCI builds in particular (which take very long time as of now - approx. 50 min.)\n+\n+2. Universal `assert` statements - pytest allows us to use the standard python assert for verifying expectations and values in tests. It also automagically provides\n+quite helpful tracebacks when tests fail.\n+\n+    **Usage**\n+\n+    Nose has a number of `assert_*` methods such as `assert_equal`, `assert_raises`, etc. Pytest on the other hand uses only plain `assert` statements.\n+    This is easier as developers don't have to remember all the methods and also pytest has awesome advanced introspection which brings great and\n+    informative tracebacks when test cases fail.\n+\n+    In nose - `assert_equal(1,1)`\n+    Equivalent statement in pytest - `assert(1==1)`\n+\n+\n+3. `parametrize` instead of `yield` bases test generators - The builtin `pytest.mark.parametrize` decorator enables parametrization of arguments for a test\n+function. This is more manageable than the nose `yield` based counterpart.\n+\n+    **Usage**\n+\n+    Nose uses yield based test generators-\n+    ```python\n+        def check_even(n, nn):\n+          print(n)\n+          assert n % 2 == 0 or nn % 2 == 0\n+\n+\n+        def test_evens():\n+          for i in range(0, 5):\n+          print(i)\n+          yield check_even, i, i * 3\n+    ```\n+\n+    Pytest on the other hand has far better and simpler `pytest.mark.parametrize`\n+    ```python\n+       @pytest.mark.parametrize(\"n, nn\", [\n+        (0, 0),\n+        pytest.mark.xfail((1, 3)),\n+        (2, 6),\n+       ])\n+       def check_even(n, nn):\n+          print(n)\n+          assert n % 2 == 0 or nn % 2 == 0\n+    ```\n+\n+4. `raises` helper - it is used to assert that some code raises an exception and is better than the decorator that is used in nose.\n+\n+    **Usage**\n+\n+    ```python\n+    def f():\n+      raise SystemExit(1)\n+\n+    def test_mytest():\n+      with pytest.raises(SystemExit):\n+        f()\n+    ```\n+\n+5. `pytest.mark.xfail` to check test cases that are expected to fail. Usage already shown above.\n+\n+**Overall the project can be divided into three major parts which will cover issues [#884](https://github.com/MDAnalysis/mdanalysis/issues/884) Port to pytest and\n+[#516](https://github.com/MDAnalysis/mdanalysis/issues/516) Update coordinate tests to use base test classes.**\n+\n+\n+Part 1\n+------\n+This part will make the bare minimum changes needed to shift from nose to pytest and get TravisCI builds along with \n+code coverage running as usual. This is being done in a single PR to avoid any weird coexistence of two testing \n+libraries.\n+\n+* Modify the code to make it discoverable by pytest (take care of `__init__` and inheritance problems as explained above)\n+* Port the 5 existing nose plugins to pytest. Pytest has awesome documentation on how to implement plugins which can be\n+ found [here](http://doc.pytest.org/en/latest/writing_plugins.html) .\n+    * Capture error (`capture_err.py`) This plugin captures stderr during test execution. If the test fails\n+    or raises an error, the captured output is appended to the error or failure output. Pytest already has this\n+feature inbuilt so this plugin can be dropped.\n+\n+        **Example**\n+        ```python\n+        def check_even(n, nn):\n+            print(n)\n+            assert n % 2 == 0 or nn % 2 == 0\n+\n+\n+        def test_evens():\n+            for i in range(0, 5):\n+                yield check_even, i, i * 3\n+        ```\n+\n+        **Output**\n+        ```\n+        ======================================= FAILURES =======================================\n+        ____________________________________ test_evens[1] _____________________________________\n+\n+        n = 1, nn = 3\n+\n+            def check_even(n, nn):\n+                print(n)\n+        >       assert n % 2 == 0 or nn % 2 == 0\n+        E       assert ((1 % 2) == 0 or (3 % 2) == 0)\n+\n+        experiments.py:37: AssertionError\n+        --------------------------------- Captured stdout call ---------------------------------\n+        1\n+        ____________________________________ test_evens[3] _____________________________________\n+\n+        n = 3, nn = 9\n+\n+            def check_even(n, nn):\n+                print(n)\n+        >       assert n % 2 == 0 or nn % 2 == 0\n+        E       assert ((3 % 2) == 0 or (9 % 2) == 0)\n+\n+        experiments.py:37: AssertionError\n+        --------------------------------- Captured stdout call ---------------------------------\n+        3\n+        ```\n+\n+\n+    * Cleanup (`cleanup.py`) This plugin deletes XDR offset files after all testing is done. This will have to\n+    be re-written as a pytest plugin.\n+    * Knownfailure (`knownfailure.py`) This is a decorator to check tests that are expected to fail. Pytest has\n+    something similar to this called `xfail`. The documentation for this is available [here](http://doc.pytest.org/en/latest/skipping.html#mark-a-test-function-as-expected-to-fail) .\n+    This needs discussion to finalize if we need to port the existing code or if `xfail` is good enough for our use.\n+    * Memory leak (`memleak.py`) This plugin detects memory leaks in tests. There is an open-source (MIT license) plugin, [pytest-leaks](https://github.com/abalkin/pytest-leaks).\n+    This also needs discussion to finalize if we need to port the existing code or if pytest-leaks is good enough for our use.\n+    * Open files (`open_files.py`) This plugin lists all open files when a test fails or errors. They also are listed at\n+    the end of the test suite. This plugin is currently disabled because we were facing issue with capturing stdout. Discussion\n+    is needed to decide if we need this plugin anymore. If we do, this will have to be rewritten for pytest as no open source\n+    alternatives are available.\n+    * To run tests in parallel, there is a open source (MIT license) plugin [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) that is recommended by pytest.\n+* Configure TravisCI and code coverage. This will not take much time as most of the original config will run just fine. Only a\n+couple of commands will have to be changed.\n+\n+**Challenges** The main challenge in this part is to not loose any tests in the conversion process. Code coverage data can be used to identify parts where this\n+ problem occurs.\n+\n+\n+Part 2 \n+------\n+This part will focus on the coordinates module and its tests. The coordinates module is a very important part of \n+MDAnalysis and any bugs in it will result in error in further calculations. Currently the coordinates module has not \n+been tested in a consistent fashion. Also all the reader/writers don’t necessarily follow the standard API.\n+\n+* Modify Reader/Writer API for files in the coordinates module for to follow the standard API.\n+* Modify coordinates tests to use base Reader/Writer test classes. This will help to set a baseline that all \n+readers/writers will have to conform to and at the same time avoid code duplication among test classes.\n+* Modify code to make use of pytest only features such as assert statements and fixtures.\n+\n+This part will be split into multiple pull requests, ideally one pull request for each coordinate test file.\n+The following files will be fixed in this part -\n+* `test_amber_inpcrd.py`\n+* `test_chainreader.py`\n+* `test_crd.py`\n+* `test_dcd.py`\n+* `test_dlpoly.py`\n+* `test_dms.py`\n+* `test_gms.py`\n+* `test_gro.py`\n+* `test_lammps.py`\n+* `test_memory.py`\n+* `test_mmtf.py`\n+* `test_mol2.py`\n+* `test_netcdf.py`\n+* `test_null.py`\n+* `test_pdb.py`\n+* `test_pdbqt.py`\n+* `test_pqr.py`\n+* `test_reader_api.py`\n+* `test_timestep_api.py`\n+* `test_trj.py`\n+* `test_trz.py`\n+* `test_writer_registration.py`\n+* `test_xdr.py`\n+* `test_xyz.py`\n+* `base.py`\n+* `reference.py`\n+\n+This part will take a large part of my time as I have to deal with two issues at the same time - [#516](https://github.com/MDAnalysis/mdanalysis/issues/516) - Porting test\n+cases to use base test classes and [#884](https://github.com/MDAnalysis/mdanalysis/issues/884) - Making changes to run with pytest. Moreover this will be the first place I\n+start using pytest idioms, so that will involve a lot of discussions and reviews from my mentors.\n+\n+**Challenges** The main challenge in this part is to modify (if needed) the reader/writer API of the coordinates module to\n+follow the API standard. Here my mentor can point out any problems with the changes via reviews. Again, this is because\n+I want to do PRs on a per-file basis - it will be easier to review and get merged.\n+\n+\n+Part 3\n+------\n+This part will focus on the remaining tests and will modify them to use pytest features such as assert statements and\n+fixtures. This part should go relatively smoothly because I will be able to follow the patterns and conventions\n+developed while working on the previous part of the project. Again, PR's will be made on a per file basis.\n+\n+\n+The following modules will be fixed in this part -\n+* `analysis` (21 files)\n+* `auxiliary` (2 files)\n+* `core` (17 files)\n+* `formats` (1 file)\n+* `lib` (1 file)\n+* `topology` (20 files)\n+* Files in the root of `testsuite` directory (19 files)\n+\n+\n+I want to work on parts 2 and 3 on a per-file basis because I find small changes are easy to review and get merged. Also\n+this way, I will be able to stick to my proposed timeline. This will also help my mentors to easily keep track of my progress\n+and help me sooner, if I were to start lagging behind or go off track.\n+\n+\n+Other Research\n+--------------\n+Just to get an approximate how much time _part 1_ of this project would take, I went ahead and did a small part of it on\n+my fork. I worked on the `coordinates` module (which is also the largest module) and made some changes to make it work\n+with pytest. Here are my findings\n+\n+**Change #1** Changed all classes that have either setUp or tearDown methods to inherit from TestCase.\n+\n+**Before** `580 failed, 954 passed, 31 skipped, 505 pytest-warnings, 26 error in 146.41 seconds`\n+\n+**After**  `171 failed, 1361 passed, 87 skipped, 42 pytest-warnings, 50 error in 177.44 seconds`\n+\n+In short, there are around 1600 test cases, by changing the base class I got around 400 more to run successfully with pytest.\n+\n+**Change #2** Use `__test__` attribute in classes which are either inheriting from other base test classes & classes that\n+are being inherited from i.e. both parent and child classes.\n+\n+**Before** `171 failed, 1361 passed, 87 skipped, 42 pytest-warnings, 50 error in 177.44 seconds`\n+\n+**After**  `1408 passed, 68 skipped`\n+\n+In short, _none of the test cases fail_.\n+\n+It is to be noted that pytest collects around 270 test cases less that nosetests, that is largely because of the fact that\n+pytest does not collect classes with an `__init__` method (which I've explained above)\n+\n+Also to get an approximate of how much time and effort _part 2_ would take, I have worked on a part of issue [#516](https://github.com/MDAnalysis/mdanalysis/issues/516)\n+where I fixed the `GRO` writers API to follow the reader API standard and modified test classes to inherit from base test classes. As of now the changes have been\n+approved and are ready to be merged. See [#1196](https://github.com/MDAnalysis/mdanalysis/pull/1196)\n+\n+\n+Schedule of Deliverables\n+========================\n+\n+### May 4th - May 29th, **Community Bonding Period**\n+\n+### May 30th - June 3rd\n+* Begin work on **Part 1**. Start by editing tests to make them discoverable by pytest.\n+Also actively discuss the `__init__` issue as it will be dealt with on a case by case\n+basis.\n+* Send a PR only for review and discussion purposes. It will _not_ be ready to merge.","path":"2017/proposals/bansal-utkarsh.md","position":512,"original_position":512,"commit_id":"c3d2a49ad4a4925e426eb6b95a2f7d9c41dc267b","original_commit_id":"c3d2a49ad4a4925e426eb6b95a2f7d9c41dc267b","user":{"login":"kain88-de","id":4302678,"avatar_url":"https://avatars3.githubusercontent.com/u/4302678?v=3","gravatar_id":"","url":"https://api.github.com/users/kain88-de","html_url":"https://github.com/kain88-de","followers_url":"https://api.github.com/users/kain88-de/followers","following_url":"https://api.github.com/users/kain88-de/following{/other_user}","gists_url":"https://api.github.com/users/kain88-de/gists{/gist_id}","starred_url":"https://api.github.com/users/kain88-de/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kain88-de/subscriptions","organizations_url":"https://api.github.com/users/kain88-de/orgs","repos_url":"https://api.github.com/users/kain88-de/repos","events_url":"https://api.github.com/users/kain88-de/events{/privacy}","received_events_url":"https://api.github.com/users/kain88-de/received_events","type":"User","site_admin":false},"body":"Your first PR's can be per module to enable increased pytest coverage.  As long as the nose coverage stays the same we can use that as smaller PR's that are easier to review. \r\n\r\nThen we have one switch PR later.  This will also depend on your progress. Even if we follow through with just one big PR we can still keep in mind to split up that work a bit.","created_at":"2017-03-28T20:49:26Z","updated_at":"2017-03-28T20:53:25Z","html_url":"https://github.com/numfocus/gsoc/pull/207#discussion_r108533476","pull_request_url":"https://api.github.com/repos/numfocus/gsoc/pulls/207","_links":{"self":{"href":"https://api.github.com/repos/numfocus/gsoc/pulls/comments/108533476"},"html":{"href":"https://github.com/numfocus/gsoc/pull/207#discussion_r108533476"},"pull_request":{"href":"https://api.github.com/repos/numfocus/gsoc/pulls/207"}}},"pull_request":{"url":"https://api.github.com/repos/numfocus/gsoc/pulls/207","id":112397828,"html_url":"https://github.com/numfocus/gsoc/pull/207","diff_url":"https://github.com/numfocus/gsoc/pull/207.diff","patch_url":"https://github.com/numfocus/gsoc/pull/207.patch","issue_url":"https://api.github.com/repos/numfocus/gsoc/issues/207","number":207,"state":"open","locked":false,"title":"Proposal: Port to pytest","user":{"login":"utkbansal","id":8345336,"avatar_url":"https://avatars1.githubusercontent.com/u/8345336?v=3","gravatar_id":"","url":"https://api.github.com/users/utkbansal","html_url":"https://github.com/utkbansal","followers_url":"https://api.github.com/users/utkbansal/followers","following_url":"https://api.github.com/users/utkbansal/following{/other_user}","gists_url":"https://api.github.com/users/utkbansal/gists{/gist_id}","starred_url":"https://api.github.com/users/utkbansal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/utkbansal/subscriptions","organizations_url":"https://api.github.com/users/utkbansal/orgs","repos_url":"https://api.github.com/users/utkbansal/repos","events_url":"https://api.github.com/users/utkbansal/events{/privacy}","received_events_url":"https://api.github.com/users/utkbansal/received_events","type":"User","site_admin":false},"body":"Checklist for prospect students for Google Summer of Code 2017:\r\n\r\n- [x] I contacted NumFOCUS mentors before writing my proposal\r\n- [x] I showed you my contribution (it can be any form: proof-of-concept project idea, some sample code or just a link to your commits from other project)\r\n- [x] I linked to my sample contribution from the proposal\r\n- [ ] I linked to my opened issues in numfocus/gsoc repository from the proposal\r\n","created_at":"2017-03-24T06:55:40Z","updated_at":"2017-03-28T20:53:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"42c0210b72cc32ee2c71990e04fd73a4a41ea13d","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/numfocus/gsoc/pulls/207/commits","review_comments_url":"https://api.github.com/repos/numfocus/gsoc/pulls/207/comments","review_comment_url":"https://api.github.com/repos/numfocus/gsoc/pulls/comments{/number}","comments_url":"https://api.github.com/repos/numfocus/gsoc/issues/207/comments","statuses_url":"https://api.github.com/repos/numfocus/gsoc/statuses/c3d2a49ad4a4925e426eb6b95a2f7d9c41dc267b","head":{"label":"utkbansal:master","ref":"master","sha":"c3d2a49ad4a4925e426eb6b95a2f7d9c41dc267b","user":{"login":"utkbansal","id":8345336,"avatar_url":"https://avatars1.githubusercontent.com/u/8345336?v=3","gravatar_id":"","url":"https://api.github.com/users/utkbansal","html_url":"https://github.com/utkbansal","followers_url":"https://api.github.com/users/utkbansal/followers","following_url":"https://api.github.com/users/utkbansal/following{/other_user}","gists_url":"https://api.github.com/users/utkbansal/gists{/gist_id}","starred_url":"https://api.github.com/users/utkbansal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/utkbansal/subscriptions","organizations_url":"https://api.github.com/users/utkbansal/orgs","repos_url":"https://api.github.com/users/utkbansal/repos","events_url":"https://api.github.com/users/utkbansal/events{/privacy}","received_events_url":"https://api.github.com/users/utkbansal/received_events","type":"User","site_admin":false},"repo":{"id":84806372,"name":"gsoc","full_name":"utkbansal/gsoc","owner":{"login":"utkbansal","id":8345336,"avatar_url":"https://avatars1.githubusercontent.com/u/8345336?v=3","gravatar_id":"","url":"https://api.github.com/users/utkbansal","html_url":"https://github.com/utkbansal","followers_url":"https://api.github.com/users/utkbansal/followers","following_url":"https://api.github.com/users/utkbansal/following{/other_user}","gists_url":"https://api.github.com/users/utkbansal/gists{/gist_id}","starred_url":"https://api.github.com/users/utkbansal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/utkbansal/subscriptions","organizations_url":"https://api.github.com/users/utkbansal/orgs","repos_url":"https://api.github.com/users/utkbansal/repos","events_url":"https://api.github.com/users/utkbansal/events{/privacy}","received_events_url":"https://api.github.com/users/utkbansal/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/utkbansal/gsoc","description":"Numfocus Google Summer of Code Materials","fork":true,"url":"https://api.github.com/repos/utkbansal/gsoc","forks_url":"https://api.github.com/repos/utkbansal/gsoc/forks","keys_url":"https://api.github.com/repos/utkbansal/gsoc/keys{/key_id}","collaborators_url":"https://api.github.com/repos/utkbansal/gsoc/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/utkbansal/gsoc/teams","hooks_url":"https://api.github.com/repos/utkbansal/gsoc/hooks","issue_events_url":"https://api.github.com/repos/utkbansal/gsoc/issues/events{/number}","events_url":"https://api.github.com/repos/utkbansal/gsoc/events","assignees_url":"https://api.github.com/repos/utkbansal/gsoc/assignees{/user}","branches_url":"https://api.github.com/repos/utkbansal/gsoc/branches{/branch}","tags_url":"https://api.github.com/repos/utkbansal/gsoc/tags","blobs_url":"https://api.github.com/repos/utkbansal/gsoc/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/utkbansal/gsoc/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/utkbansal/gsoc/git/refs{/sha}","trees_url":"https://api.github.com/repos/utkbansal/gsoc/git/trees{/sha}","statuses_url":"https://api.github.com/repos/utkbansal/gsoc/statuses/{sha}","languages_url":"https://api.github.com/repos/utkbansal/gsoc/languages","stargazers_url":"https://api.github.com/repos/utkbansal/gsoc/stargazers","contributors_url":"https://api.github.com/repos/utkbansal/gsoc/contributors","subscribers_url":"https://api.github.com/repos/utkbansal/gsoc/subscribers","subscription_url":"https://api.github.com/repos/utkbansal/gsoc/subscription","commits_url":"https://api.github.com/repos/utkbansal/gsoc/commits{/sha}","git_commits_url":"https://api.github.com/repos/utkbansal/gsoc/git/commits{/sha}","comments_url":"https://api.github.com/repos/utkbansal/gsoc/comments{/number}","issue_comment_url":"https://api.github.com/repos/utkbansal/gsoc/issues/comments{/number}","contents_url":"https://api.github.com/repos/utkbansal/gsoc/contents/{+path}","compare_url":"https://api.github.com/repos/utkbansal/gsoc/compare/{base}...{head}","merges_url":"https://api.github.com/repos/utkbansal/gsoc/merges","archive_url":"https://api.github.com/repos/utkbansal/gsoc/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/utkbansal/gsoc/downloads","issues_url":"https://api.github.com/repos/utkbansal/gsoc/issues{/number}","pulls_url":"https://api.github.com/repos/utkbansal/gsoc/pulls{/number}","milestones_url":"https://api.github.com/repos/utkbansal/gsoc/milestones{/number}","notifications_url":"https://api.github.com/repos/utkbansal/gsoc/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/utkbansal/gsoc/labels{/name}","releases_url":"https://api.github.com/repos/utkbansal/gsoc/releases{/id}","deployments_url":"https://api.github.com/repos/utkbansal/gsoc/deployments","created_at":"2017-03-13T09:13:13Z","updated_at":"2017-03-13T09:13:15Z","pushed_at":"2017-03-24T06:54:17Z","git_url":"git://github.com/utkbansal/gsoc.git","ssh_url":"git@github.com:utkbansal/gsoc.git","clone_url":"https://github.com/utkbansal/gsoc.git","svn_url":"https://github.com/utkbansal/gsoc","homepage":"http://numfocus.org/programs/gsoc.html","size":2442,"stargazers_count":0,"watchers_count":0,"language":"TeX","has_issues":false,"has_projects":true,"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":"numfocus:master","ref":"master","sha":"daaec76bdff8b79c52bba1b93068bf3e0b9c4795","user":{"login":"numfocus","id":1421094,"avatar_url":"https://avatars2.githubusercontent.com/u/1421094?v=3","gravatar_id":"","url":"https://api.github.com/users/numfocus","html_url":"https://github.com/numfocus","followers_url":"https://api.github.com/users/numfocus/followers","following_url":"https://api.github.com/users/numfocus/following{/other_user}","gists_url":"https://api.github.com/users/numfocus/gists{/gist_id}","starred_url":"https://api.github.com/users/numfocus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/numfocus/subscriptions","organizations_url":"https://api.github.com/users/numfocus/orgs","repos_url":"https://api.github.com/users/numfocus/repos","events_url":"https://api.github.com/users/numfocus/events{/privacy}","received_events_url":"https://api.github.com/users/numfocus/received_events","type":"Organization","site_admin":false},"repo":{"id":30978155,"name":"gsoc","full_name":"numfocus/gsoc","owner":{"login":"numfocus","id":1421094,"avatar_url":"https://avatars2.githubusercontent.com/u/1421094?v=3","gravatar_id":"","url":"https://api.github.com/users/numfocus","html_url":"https://github.com/numfocus","followers_url":"https://api.github.com/users/numfocus/followers","following_url":"https://api.github.com/users/numfocus/following{/other_user}","gists_url":"https://api.github.com/users/numfocus/gists{/gist_id}","starred_url":"https://api.github.com/users/numfocus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/numfocus/subscriptions","organizations_url":"https://api.github.com/users/numfocus/orgs","repos_url":"https://api.github.com/users/numfocus/repos","events_url":"https://api.github.com/users/numfocus/events{/privacy}","received_events_url":"https://api.github.com/users/numfocus/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/numfocus/gsoc","description":"Numfocus Google Summer of Code Materials","fork":false,"url":"https://api.github.com/repos/numfocus/gsoc","forks_url":"https://api.github.com/repos/numfocus/gsoc/forks","keys_url":"https://api.github.com/repos/numfocus/gsoc/keys{/key_id}","collaborators_url":"https://api.github.com/repos/numfocus/gsoc/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/numfocus/gsoc/teams","hooks_url":"https://api.github.com/repos/numfocus/gsoc/hooks","issue_events_url":"https://api.github.com/repos/numfocus/gsoc/issues/events{/number}","events_url":"https://api.github.com/repos/numfocus/gsoc/events","assignees_url":"https://api.github.com/repos/numfocus/gsoc/assignees{/user}","branches_url":"https://api.github.com/repos/numfocus/gsoc/branches{/branch}","tags_url":"https://api.github.com/repos/numfocus/gsoc/tags","blobs_url":"https://api.github.com/repos/numfocus/gsoc/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/numfocus/gsoc/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/numfocus/gsoc/git/refs{/sha}","trees_url":"https://api.github.com/repos/numfocus/gsoc/git/trees{/sha}","statuses_url":"https://api.github.com/repos/numfocus/gsoc/statuses/{sha}","languages_url":"https://api.github.com/repos/numfocus/gsoc/languages","stargazers_url":"https://api.github.com/repos/numfocus/gsoc/stargazers","contributors_url":"https://api.github.com/repos/numfocus/gsoc/contributors","subscribers_url":"https://api.github.com/repos/numfocus/gsoc/subscribers","subscription_url":"https://api.github.com/repos/numfocus/gsoc/subscription","commits_url":"https://api.github.com/repos/numfocus/gsoc/commits{/sha}","git_commits_url":"https://api.github.com/repos/numfocus/gsoc/git/commits{/sha}","comments_url":"https://api.github.com/repos/numfocus/gsoc/comments{/number}","issue_comment_url":"https://api.github.com/repos/numfocus/gsoc/issues/comments{/number}","contents_url":"https://api.github.com/repos/numfocus/gsoc/contents/{+path}","compare_url":"https://api.github.com/repos/numfocus/gsoc/compare/{base}...{head}","merges_url":"https://api.github.com/repos/numfocus/gsoc/merges","archive_url":"https://api.github.com/repos/numfocus/gsoc/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/numfocus/gsoc/downloads","issues_url":"https://api.github.com/repos/numfocus/gsoc/issues{/number}","pulls_url":"https://api.github.com/repos/numfocus/gsoc/pulls{/number}","milestones_url":"https://api.github.com/repos/numfocus/gsoc/milestones{/number}","notifications_url":"https://api.github.com/repos/numfocus/gsoc/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/numfocus/gsoc/labels{/name}","releases_url":"https://api.github.com/repos/numfocus/gsoc/releases{/id}","deployments_url":"https://api.github.com/repos/numfocus/gsoc/deployments","created_at":"2015-02-18T17:54:16Z","updated_at":"2017-03-28T13:03:59Z","pushed_at":"2017-03-28T18:52:35Z","git_url":"git://github.com/numfocus/gsoc.git","ssh_url":"git@github.com:numfocus/gsoc.git","clone_url":"https://github.com/numfocus/gsoc.git","svn_url":"https://github.com/numfocus/gsoc","homepage":"http://numfocus.org/programs/gsoc.html","size":2409,"stargazers_count":66,"watchers_count":66,"language":"TeX","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":108,"mirror_url":null,"open_issues_count":33,"forks":108,"open_issues":33,"watchers":66,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/numfocus/gsoc/pulls/207"},"html":{"href":"https://github.com/numfocus/gsoc/pull/207"},"issue":{"href":"https://api.github.com/repos/numfocus/gsoc/issues/207"},"comments":{"href":"https://api.github.com/repos/numfocus/gsoc/issues/207/comments"},"review_comments":{"href":"https://api.github.com/repos/numfocus/gsoc/pulls/207/comments"},"review_comment":{"href":"https://api.github.com/repos/numfocus/gsoc/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/numfocus/gsoc/pulls/207/commits"},"statuses":{"href":"https://api.github.com/repos/numfocus/gsoc/statuses/c3d2a49ad4a4925e426eb6b95a2f7d9c41dc267b"}}}}
{"actor":{"display_login":"kain88-de"}}