diff --git a/.gitignore b/.gitignore index de35dbe..241d83f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,17 @@ -.DS_Store -.coverage -.noseids -.ropeproject/ -.tox/ -*.pyc -*~ -*.sqlite -*.sqlite-journal -settings_local.py -local_settings.py +*.egg-info/ + .*.sw[po] +*.py[co] + +build/ dist/ -*.egg-info doc/.build/ -build/ -locale/ +.ropeproject/ +.tox/ + +ChangeLog pip-log.txt + +.DS_Store +.coverage +.noseids diff --git a/.ucopy.cfg b/.ucopy.cfg new file mode 100644 index 0000000..530185a --- /dev/null +++ b/.ucopy.cfg @@ -0,0 +1,21 @@ +[repo] +name = python-github2 +license-from = bsd-3 +vcs = git + +[files] +ignore-from = git +ignore-glob = COPYING, README.rst, .keep, test/data/github.com,api* + +[patch] +unicode = python + +[author-override] +Ask Solem = +Chris Vale = +Claudio B. = +Daniel Greenfeld = +Donald von Stufft = +Michael Basnight = +Sameer Al-Sakran = +Stéphane Angel = diff --git a/AUTHORS b/AUTHORS index 66d4ec9..5e14ae3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,30 +1,38 @@ -Ask Solem -Mark Paschal -Donald von Stufft -Maximillian Dornseif +python-github2 was written by: + +Ask Solem + +with contributions from the following people: + +Adam Vandenberg Asheesh Laroia -Rick Harris -Cody Soyland -Fernando Perez -Evan Broder -Scott Torborg -Claudio B. +Barthelemy Dagenais Chris Vale -Adam Vandenberg -Kenneth Reitz +Christopher MacGown +Claudio B. +Cody Soyland Daniel Greenfeld +Donald von Stufft +Evan Broder +Fernando Perez +Ionuț Arțăriși +James Rowe +Jens Ohlig Jeremy Dunck Jonas Obrist -Sameer Al-Sakran -Vincent Driessen -James Rowe Josh Weinberg -Barthelemy Dagenais -Surajram Kumarave -broderboy -Patryk Zawadzki +Justin Quick +Kenneth Reitz +Mark Paschal +Maximillian Dornseif Michael Basnight -Christopher MacGown +Patryk Zawadzki +Rick Harris Rok Garbas -Ionuț Arțăriși +Sameer Al-Sakran +Scott Torborg Stéphane Angel +Surajram Kumaravel +Vincent Driessen +broderboy +modocache diff --git a/MANIFEST.in b/MANIFEST.in index ac52cc3..e77f35d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,6 @@ recursive-include examples * + +include ChangeLog include NEWS.rst include README.rst include AUTHORS diff --git a/NEWS.rst b/NEWS.rst index cebb8ac..eaa874c 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,7 +1,33 @@ User-visible changes ==================== -.. contents:: +This file lists only the most important changes that may be visible to users, +look at the `git repository`_ for the full project history. + +.. _git repository: https://github.com/ask/python-github2/ + +0.6.2 - 2012-06-11 +------------------ + +* Updated dependencies to handle recent ``python-dateutil`` releases +* Fixed ``simplejson`` dependencies for Python 2.4 + +0.6.1 - 2012-02-28 +------------------ + +* Support for `GitHub:Enterprise`_ using the ``github_url`` parameter when + creating a client +* Adds SSH key management +* Support reading SSL certificates from the location set in ``CURL_CA_BUNDLE``, + if all else fails + +.. _GitHub:Enterprise: https://enterprise.github.com/ + +0.6.0 - 2011-12-21 +------------------ + +* Now uses system certificates for SSL validation where possible +* Python 3 is supported directly, without a separate ``2to3`` build step 0.5.2 - 2011-09-02 ------------------ diff --git a/README.rst b/README.rst index 0746644..a91633f 100644 --- a/README.rst +++ b/README.rst @@ -1,18 +1,29 @@ ================================================================================ -github2 - Github API v2 library for Python. +github2 - GitHub API v2 library for Python. ================================================================================ :Authors: Ask Solem (askh@opera.com) -:Version: 0.5.2 +:Version: 0.6.2 + +.. warning:: + + GitHub have marked API v2 as deprecated, you should be looking to replace + your usage of ``github2`` in the near future. + + Both remoteobjects_ and micromodels_ provide easy-to-use functionality for + creating bindings to remote APIs, and should significantly reduce the amount + of work needed in moving away from GitHub's API v2. This is a Python library implementing all of the features available in version 2 -of the `Github API`_. +of the `GitHub API`_. See the ``doc/`` directory for installation instructions and usage information. If you prefer you can also read the `documentation online`_. -.. _Github API: http://develop.github.com/ +.. _remoteobjects: https://github.com/saymedia/remoteobjects +.. _micromodels: https://github.com/j4mie/micromodels +.. _GitHub API: http://develop.github.com/ .. _documentation online: http://packages.python.org/github2 License diff --git a/doc/.templates/layout.html b/doc/.templates/layout.html index 3560b06..b251a03 100644 --- a/doc/.templates/layout.html +++ b/doc/.templates/layout.html @@ -1,3 +1,10 @@ +{# + Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. +#} {% extends "!layout.html" %} {% block extrahead %} diff --git a/doc/api/client.rst b/doc/api/client.rst index 4edc07b..1cf0ea9 100644 --- a/doc/api/client.rst +++ b/doc/api/client.rst @@ -1,3 +1,11 @@ +.. Copyright (C) 2011-2012 James Rowe + Michael Basnight + modocache + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.client Creating a client @@ -42,12 +50,25 @@ API calls are limited by github.com to 1 per second by default. To have the >>> github = Github(username="ask", api_token=".......", ... requests_per_second=1) -If you wish to use a HTTP proxy you can pass in the ``proxy_host`` and -``proxy_port`` settings to enable it. The default for ``proxy_port``, if not -given, is 8080:: +By default, :pypi:`httplib2 (0.7.4)` will use the proxies set in the +:envvar:`http_proxy` andr :envvar:`https_proxy` environment variables. This +means that well configured systems shouldn't need any manual configuration for +proxy support. + +If you wish to manually configure a HTTP proxy you can pass in the +``proxy_host`` and ``proxy_port`` settings to enable it. The default for +``proxy_port``, if not given, is 8080:: >>> from github2.client import Github >>> github = Github(username="ask", api_token=".......", ... proxy_host="my.proxy.com", proxy_port=9000) +You may specify a GitHub Enterprise URL by passing in the ``github_url`` +setting. If you do not specify ``github_url``, requests will be made to +https://github.com/. + + >>> from github2.client import Github + >>> github = Github(username="modocache", api_token=".......", + ... github_url="http://your-github-enterprise-url.com/") + .. _OAuth service: http://develop.github.com/p/oauth.html diff --git a/doc/api/commit.rst b/doc/api/commit.rst index e4a69bf..ffca1a2 100644 --- a/doc/api/commit.rst +++ b/doc/api/commit.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.commits Commit diff --git a/doc/api/core.rst b/doc/api/core.rst index bb96daf..1e26d09 100644 --- a/doc/api/core.rst +++ b/doc/api/core.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.core Core @@ -30,10 +36,10 @@ Core .. autofunction:: repr_string -.. autoclass:: GithubCommand(type) +.. autoclass:: GithubCommand -.. autoclass:: Attribute(type) +.. autoclass:: Attribute -.. autoclass:: DateAttribute(type) +.. autoclass:: DateAttribute(help, format) -.. autoclass:: BaseDataType(type) +.. autoclass:: BaseData() diff --git a/doc/api/index.rst b/doc/api/index.rst index cbe2c5c..36fa3a0 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + API documentation ================= diff --git a/doc/api/issues.rst b/doc/api/issues.rst index 6ba740c..1f2dcfb 100644 --- a/doc/api/issues.rst +++ b/doc/api/issues.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.issues Issues diff --git a/doc/api/network.rst b/doc/api/network.rst index f07baa9..1d7312d 100644 --- a/doc/api/network.rst +++ b/doc/api/network.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. currentmodule:: github2.client Network diff --git a/doc/api/object.rst b/doc/api/object.rst index 79842c7..106b685 100644 --- a/doc/api/object.rst +++ b/doc/api/object.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. currentmodule:: github2.client Object diff --git a/doc/api/organizations.rst b/doc/api/organizations.rst index fc46950..b4fb0f6 100644 --- a/doc/api/organizations.rst +++ b/doc/api/organizations.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.organizations Organizations diff --git a/doc/api/pull_requests.rst b/doc/api/pull_requests.rst index 140a824..ee6f518 100644 --- a/doc/api/pull_requests.rst +++ b/doc/api/pull_requests.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.pull_requests Pull requests diff --git a/doc/api/repos.rst b/doc/api/repos.rst index 4f777ee..1d96337 100644 --- a/doc/api/repos.rst +++ b/doc/api/repos.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.repositories Repository diff --git a/doc/api/request.rst b/doc/api/request.rst index 395f39f..b3ffc49 100644 --- a/doc/api/request.rst +++ b/doc/api/request.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.request Requests @@ -8,7 +14,10 @@ Requests of the :mod:`github2` package, but it is documented to aid contributors to the package. -.. autodata:: GITHUB_URL +.. autodata:: DEFAULT_GITHUB_URL + +.. autodata:: SYSTEM_CERTS +.. autodata:: CURL_CERTS .. autoexception:: GithubError diff --git a/doc/api/teams.rst b/doc/api/teams.rst index dc2102b..4e72746 100644 --- a/doc/api/teams.rst +++ b/doc/api/teams.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.teams Teams diff --git a/doc/api/users.rst b/doc/api/users.rst index 36f086d..d9ac95d 100644 --- a/doc/api/users.rst +++ b/doc/api/users.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + .. module:: github2.users Users @@ -13,6 +19,8 @@ Users .. autoclass:: Users(type) +.. autoclass:: Key(type) + Examples -------- diff --git a/doc/bugs.rst b/doc/bugs.rst index 2226397..1c838a1 100644 --- a/doc/bugs.rst +++ b/doc/bugs.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + Reporting bugs ============== @@ -24,7 +30,7 @@ or a deficiency in the API. You can check which URLs your code is requesting by enabling :data:`~logging.DEBUG` level output in your logger, see the -:mod:`python:logging` for more information. +:mod:`python:logging` documentation for details. If the bug you've found is outside the reach of this project an issue should be opened in GitHub's `API support forum`_. It doesn't hurt to `report an issue`_ @@ -45,10 +51,10 @@ A good bug report will have the following: * A minimal test-case to reproduce the error * A list of solutions you've already tried -Simon Tatham has an excellent essay titled `How to Report Bugs Effectively`_, +Simon Tatham wrote aa fantastic essay titled `How to Report Bugs Effectively`_, with some excellent tips on filing good bug reports. -.. [#] The content of :data:`github2.__version__` if you're using an official +.. [#] The value of :data:`github2.__version__` if you're using an official release, or the output of :command:`git describe` if you're using the git repository directly. diff --git a/doc/conf.py b/doc/conf.py index 2f2b3bf..d74babe 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -3,7 +3,8 @@ # python-github2 documentation build configuration file, created by # sphinx-quickstart on Mon Apr 11 16:16:25 2011. # -# This file is execfile()d with the current directory set to its containing dir. +# This file is execfile()d with the current directory set to its containing +# dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -11,7 +12,10 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys + +from sphinx.util import inspect import cloud_sptheme as csp @@ -20,15 +24,16 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) -# -- General configuration ----------------------------------------------------- +# -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ["sphinx.ext.%s" % ext - for ext in ["autodoc", "todo", "intersphinx", "viewcode"]] + \ + for ext in ["autodoc", "todo", "intersphinx", "viewcode", + "coverage"]] + \ ["sphinxcontrib.%s" % ext for ext in ["cheeseshop", ]] # Add any paths that contain templates here, relative to this directory. @@ -45,7 +50,7 @@ # General information about the project. project = u'github2' -copyright = u'2011, Ask Solem' +copyright = u'2009-2012, Ask Solem' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -71,7 +76,8 @@ # directories to ignore when looking for source files. exclude_patterns = ['.build'] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) to use for all +# documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. @@ -92,7 +98,7 @@ #modindex_common_prefix = [] -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -174,7 +180,7 @@ htmlhelp_basename = 'github2doc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' @@ -183,7 +189,8 @@ #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). +# (source start file, target name, title, author, +# documentclass [howto/manual]). latex_documents = [ ('index', 'github2.tex', u'github2 Documentation', u'Ask Solem', 'manual'), @@ -213,7 +220,7 @@ #latex_domain_indices = True -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). @@ -222,9 +229,22 @@ [u'Ask Solem'], 1) ] -autoclass_content = "init" +autoclass_content = "class" autodoc_default_flags = ['members', ] intersphinx_mapping = { 'python': ('http://docs.python.org/', os.getenv('SPHINX_PYTHON_OBJECTS')) } + + +# Horrific nastiness to generate correct function signature for decorated +# objects. Close your eyes... Now! +orig_getargspec = inspect.getargspec + + +def getargspec(func): + if hasattr(func, '__orig_func__'): + return orig_getargspec(func.__orig_func__) + else: + return orig_getargspec(func) +inspect.getargspec = getargspec diff --git a/doc/contributing.rst b/doc/contributing.rst index ad3f31c..d307ba3 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -1,12 +1,18 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + Contributing ============ Patches for :mod:`github2` are most welcome! Forks on GitHub_ and patches attached to issues are both great ways to -contribute. If you're comfortable with ``git`` using a fork hosted on GitHub is -probably the simpler solution, both for the package maintainers and you as a -contributor. +contribute. If you're comfortable with ``git`` then using a fork hosted on +GitHub is probably the simpler solution, both for the :mod:`github2` maintainers +and for you as a contributor. Following the simple guidelines below makes it easier to review and integrate your changes: @@ -71,9 +77,9 @@ Many assertions, such as :meth:`~unittest.TestCase.assertIn` and The simple workaround is to evaluate an expression to test with :meth:`~unittest.TestCase.assertTrue` -The incredibly functions for skipping tests(:func:`~unittest.skip`) and marking -expected failures(:func:`~unittest.expectedFailure`) were only added in 2.7, and -unfortunately can't be used. +The incredibly useful functions for skipping tests(:func:`~unittest.skip`) and +marking expected failures(:func:`~unittest.expectedFailure`) were only added in +2.7, and unfortunately can't be used. .. todo:: Add topic branches and pull request usage examples, but most git users are diff --git a/doc/index.rst b/doc/index.rst index 8656174..963e6a2 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,7 +1,8 @@ -.. python-github2 documentation master file, created by - sphinx-quickstart on Mon Apr 11 16:16:25 2011. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. .. If you prefer you can also read the pre-built documentation at @@ -10,23 +11,35 @@ .. module:: github2 :synopsis: GitHub API v2 library for Python -``github2`` - Github API v2 library for Python +``github2`` - GitHub API v2 library for Python ============================================== +.. warning:: + + GitHub have marked API v2 as deprecated, you should be looking to replace + your usage of ``github2`` in the near future. + + Both remoteobjects_ and micromodels_ provide easy-to-use functionality for + creating bindings to remote APIs, and should significantly reduce the amount + of work needed in moving away from GitHub's API v2. + +.. _remoteobjects: https://github.com/saymedia/remoteobjects +.. _micromodels: https://github.com/j4mie/micromodels + .. pypi-release:: github2 :prefix: Download :class: sidebar This is a Python library implementing all of the features available in version 2 -of the `Github API`_. +of the `GitHub API`_. -You should read the developer documentation for the `Github API`_ first. +You should read the developer documentation for the `GitHub API`_ first. :Git repository: https://github.com/ask/python-github2/ :Issue tracker: https://github.com/ask/python-github2/issues/ :Contributors: https://github.com/ask/python-github2/contributors/ -.. _Github API: http://develop.github.com/ +.. _GitHub API: http://develop.github.com/ Contents -------- @@ -41,6 +54,7 @@ Contents bugs contributing wild + release license Indices and tables diff --git a/doc/install.rst b/doc/install.rst index 8d309ee..7eb1aa6 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -1,3 +1,10 @@ +.. Copyright (C) 2011-2012 James Rowe + Michael Basnight + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + Installation ------------ @@ -26,5 +33,6 @@ handling [#]_. :pypi:`simplejson` is also required when using :mod:`github2` with Python 2.4 or 2.5. If you install via :pypi:`pip` or :pypi:`easy_install ` the dependencies should be installed automatically for you. -.. [#] You must use :pypi:`python-dateutil` 1.x when working with Python 2.x, - the latest 2.x releases are for Python 3.x installations only. +.. [#] You must use :pypi:`python-dateutil` 1.x when working with Python 2.5 or + earlier, the latest 2.x releases are for Python 2.6 and newer + installations only. diff --git a/doc/license.rst b/doc/license.rst index 7e1c1b4..316b2ae 100644 --- a/doc/license.rst +++ b/doc/license.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + License ======= diff --git a/doc/problems.rst b/doc/problems.rst index 90021af..31d3052 100644 --- a/doc/problems.rst +++ b/doc/problems.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + Solving problems ================ @@ -35,14 +41,14 @@ response from GitHub, here the user data of ``JNRowe``. Rate limiting ''''''''''''' -If rate limiting is enabled, with the ``requests_per_second`` when creating a -:class:`~github2.client.Github` object, then you'll see a ``WARNING`` level -message when a request has been delayed. +If rate limiting is enabled, with the ``requests_per_second`` parameter when +creating a :class:`~github2.client.Github` object, then you'll see a ``WARNING`` +level message when a request has been delayed. - >>> github = Github(requests_per_second=0.2, debug=True) + >>> github = Github(requests_per_second=0.2) >>> user = github.users.show("JNRowe") >>> user = github.users.show("JNRowe") - delaying API call 4.99773 + WARNING:github2.request:delaying API call 4.997032 second(s) Here we have defined a rate limit of one call every five seconds, and doing so has imposed an almost 5 second delay before completing the second request. diff --git a/doc/quickstart.rst b/doc/quickstart.rst index bb41f5e..ff54b29 100644 --- a/doc/quickstart.rst +++ b/doc/quickstart.rst @@ -1,3 +1,9 @@ +.. Copyright (C) 2011-2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + Quickstart ========== @@ -7,6 +13,7 @@ package. Create an unauthenticated client object:: + >>> from github2.client import Github >>> github = Github() .. note:: @@ -22,6 +29,7 @@ Read the description of the ``python-github2`` project:: >>> repo = github.repos.show("ask/python-github2") >>> repo.description + u'github client in python, with issues support.' We can take advantage of Python's :func:`dir` to explore the package a little more:: diff --git a/doc/release.rst b/doc/release.rst new file mode 100644 index 0000000..745d419 --- /dev/null +++ b/doc/release.rst @@ -0,0 +1,69 @@ +.. Copyright (C) 2012 James Rowe + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + +Release HOWTO +============= + +.. highlight:: sh + +.. + Much of this stuff is automated locally, but I'm describing the process for + other people who will not have access to the same release tools I use. The + first thing I recommend that you do is find/write a tool that allows you to + automate all of this, or you're going to miss important steps at some point. + +Test +---- + +In the general case tests can be run via :pypi:`nose`'s :pypi:`distribute` +integration:: + + $ ./setup.py nosetests + +When preparing a release it is important to check that :mod:`github2` works with +all currently supported Python versions, and that the documentation is correct. +To that end you can use :pypi:`tox` to run the full testsuite:: + + $ tox -v + +This will test :mod:`github2` with Python 2.4 → 3.2, check that the ``reST`` +syntax is valid and also that the :pypi:`sphinx` documentation builds correctly. +You can run a subset of these options too, see the ``tox`` documentation for +more information. + +Prepare release +--------------- + +With the tests passing, perform the following steps + +* Update the version data in :file:`github2/_version.py`, and also the + reference in :file:`README.rst` +* Update :file:`NEWS.rst`, if there are any user visible changes +* Commit the release notes and version changes +* Create a signed tag for the release +* Push the changes, including the new tag, to the GitHub repository + +Update PyPI +----------- + +.. + This is the section you're especially likely to get wrong at some point if you + try to handle all of this manually ;) + +Create and upload the new release tarballs to PyPI:: + + $ ./setup.py sdist --formats=bztar,gztar register upload --sign + +You should also update the hosted documentation too:: + + $ ./setup.py build_sphinx && ./setup.py upload_docs + +Fetch the uploaded tarballs, and check for errors. + +You should also perform test installations from PyPI, to check the experience +:mod:`github2` users will have. + +.. highlight:: python diff --git a/doc/wild.rst b/doc/wild.rst index 6ee7751..07bd653 100644 --- a/doc/wild.rst +++ b/doc/wild.rst @@ -1,3 +1,10 @@ +.. Copyright (C) 2011-2012 James Rowe + Stéphane Angel + + This file is part of python-github2, and is licensed under the 3-clause BSD + License. See the LICENSE file in the top distribution directory for the full + license text. + In the wild ----------- @@ -15,14 +22,29 @@ listed on this page. Applause when a bug gets closed. Extra cheering for old bugs. -:PyPI page: :pypi:`applause` +:PyPI project: :pypi:`applause` ``bitbucket2github`` '''''''''''''''''''' Mirrors all public repos of a BitBucket account to GitHub and vice versa. -:PyPI page: :pypi:`bitbucket2github` +:PyPI project: :pypi:`bitbucket2github` + +``bugwarrior`` +'''''''''''''' + + Pull tickets from github, bitbucket, and trac into `taskwarrior + `__ + +:PyPI project: :pypi:`bugwarrior` + +``cligh`` +''''''''' + + A simple command-line interface to the facilities of GitHub. + +:GitHub project: https://github.com/CMB/cligh ``forkfeed`` '''''''''''' @@ -30,7 +52,7 @@ listed on this page. Utility to build atom feeds for all commits in all forks of your projects on GitHub. -:PyPI page: :pypi:`forkfeed` +:PyPI project: :pypi:`forkfeed` ``ghmiles`` ''''''''''' @@ -38,7 +60,37 @@ listed on this page. ``ghmiles`` is a Python library that generates a milestone model from the issues in a GitHub repository. -:PyPI page: :pypi:`ghmiles` +:PyPI project: :pypi:`ghmiles` + +``ghsync`` +'''''''''' + + GitHub Syncer. Clones or Pulls all GitHub repos. + +:PyPI project: :pypi:`ghsync` + +``github-issues`` +''''''''''''''''' + + github-issues allows you to create, close, show, list, and comment on + issues on your github project - that's it. + +:GitHub project: https://github.com/kashifrazzaqui/github-issues + +``github-issues-export`` +'''''''''''''''''''''''' + + Script to liberate issues from github issues in case you would like to have + your data free. + +:GitHub project: https://github.com/mcepl/github-issues-export + +``github-plots`` +'''''''''''''''' + + Alternative plots from GitHub stats. + +:PyPI project: :pypi:`github-plots` ``hubugs`` '''''''''' @@ -46,10 +98,17 @@ listed on this page. ``hubugs`` is a very simple client for working with `GitHub's issue tracker`_. -:PyPI page: :pypi:`hubugs` +:PyPI project: :pypi:`hubugs` .. _GitHub's issue tracker: http://github.com/blog/411-github-issue-tracker +``humble`` +'''''''''' + + Shows stats on a given GitHub user. + +:PyPI project: :pypi:`humble` + ``Repos.io`` '''''''''''' @@ -57,7 +116,7 @@ listed on this page. repositories (your own, and watched/liked/followed ones) hosted by different providers (github, bitbucket) -:Git repository: https://github.com/twidi/Repos.io +:GitHub project: https://github.com/twidi/Repos.io ``roundabout`` '''''''''''''' @@ -65,7 +124,7 @@ listed on this page. ``Roundabout`` is a tool that automatically prevents code with failing tests from being merged into a github repository. -:Git repository: https://github.com/ChristopherMacGown/roundabout +:GitHub project: https://github.com/ChristopherMacGown/roundabout .. _open an issue: https://github.com/ask/python-github2/issues/ .. _project website: https://github.com/ask/python-github2/blob/master/doc/wild.rst diff --git a/examples/friend-or-follow.py b/examples/friend-or-follow.py index ccef96a..7b87edc 100644 --- a/examples/friend-or-follow.py +++ b/examples/friend-or-follow.py @@ -1,3 +1,8 @@ +# Copyright (C) 2010-2012 Ask Solem +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + import sys import optparse from subprocess import Popen, PIPE @@ -10,10 +15,10 @@ OPTION_LIST = ( optparse.make_option('-t', '--api-token', default=None, action="store", dest="api_token", type="str", - help="Github API token. Default is to find this from git config"), + help="GitHub API token. Default is to find this from git config"), optparse.make_option('-u', '--api-user', default=None, action="store", dest="api_user", type="str", - help="Github Username. Default is to find this from git config"), + help="GitHub Username. Default is to find this from git config"), ) BY_LOWER = lambda value: value.lower() diff --git a/extra/requirements-doc.txt b/extra/requirements-doc.txt new file mode 100644 index 0000000..709ee46 --- /dev/null +++ b/extra/requirements-doc.txt @@ -0,0 +1,4 @@ +-r requirements.txt +cloud_sptheme>=1.2 +sphinx>=1.1 +sphinxcontrib-cheeseshop diff --git a/extra/requirements-py25.txt b/extra/requirements-py25.txt new file mode 100644 index 0000000..45bfac5 --- /dev/null +++ b/extra/requirements-py25.txt @@ -0,0 +1,3 @@ +httplib2 >= 0.7.0 +python-dateutil < 2.0, >= 2.1 +simplejson >= 2.0.9 diff --git a/extra/requirements-test.txt b/extra/requirements-test.txt new file mode 100644 index 0000000..d271169 --- /dev/null +++ b/extra/requirements-test.txt @@ -0,0 +1,4 @@ +-r requirements.txt +coverage>=3.5 +mock>=0.7.1 +nose>=1.1.2 diff --git a/extra/requirements.txt b/extra/requirements.txt new file mode 100644 index 0000000..5115300 --- /dev/null +++ b/extra/requirements.txt @@ -0,0 +1,2 @@ +httplib2 >= 0.7.0 +python-dateutil < 2.0 diff --git a/github2/__init__.py b/github2/__init__.py index ce95e04..f098fc2 100644 --- a/github2/__init__.py +++ b/github2/__init__.py @@ -1,7 +1,16 @@ -"Github API v2 library for Python" -VERSION = (0, 5, 2) +"GitHub API v2 library for Python." +# Copyright (C) 2009-2012 Ask Solem +# James Rowe +# Maximillian Dornseif +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + +from github2 import _version + +VERSION = _version.tuple __author__ = "Ask Solem" __contact__ = "askh@opera.com" __homepage__ = "http://github.com/ask/python-github2" -__version__ = ".".join(map(str, VERSION)) +__version__ = _version.dotted diff --git a/github2/_version.py b/github2/_version.py new file mode 100644 index 0000000..6e26b1f --- /dev/null +++ b/github2/_version.py @@ -0,0 +1,9 @@ +# This is github2 version 0.6.2 (2012-06-11) +# pylint: disable=C0103, C0111, C0121, W0622 + +dotted = "0.6.2" +libtool = "6:22" +hex = 0x000602 +date = "2012-06-11" +tuple = (0, 6, 2) +web = "github2/0.6.2" diff --git a/github2/bin/__init__.py b/github2/bin/__init__.py index 792d600..6c5b974 100644 --- a/github2/bin/__init__.py +++ b/github2/bin/__init__.py @@ -1 +1,5 @@ # +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. diff --git a/github2/bin/manage_collaborators.py b/github2/bin/manage_collaborators.py index ca59df5..b63ab6c 100755 --- a/github2/bin/manage_collaborators.py +++ b/github2/bin/manage_collaborators.py @@ -8,16 +8,38 @@ """ # Created by Maximillian Dornseif on 2009-12-31 for HUDORA. -# Copyright (c) 2009 HUDORA. All rights reserved. -# BSD licensed +# Copyright (C) 2009-2012 James Rowe +# Maximillian Dornseif +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. import logging +import sys + from optparse import OptionParser + import github2.client +#: Running under Python 3 +PY3K = sys.version_info[0] == 3 and True or False + + +def print_(text): + """Python 2 & 3 compatible print function. + + We support <2.6, so can't use __future__.print_function + + """ + if PY3K: + print(text) + else: + sys.stdout.write(text + '\n') + + def parse_commandline(): - """Parse the comandline and return parsed options.""" + """Parse the command line and return parsed options.""" parser = OptionParser() parser.description = __doc__ @@ -41,11 +63,14 @@ def parse_commandline(): if len(args) not in [1, 2]: parser.error('wrong number of arguments') if (len(args) == 1 and args[0] in ['add', 'remove']): - parser.error('%r needs a collaborator name as second parameter\n' % args[0]) + parser.error('%r needs a collaborator name as second parameter\n' + % args[0]) elif (len(args) == 1 and args[0] != 'list'): - parser.error('unknown command %r. Try "list", "add" or "remove"\n' % args[0]) + parser.error('unknown command %r. Try "list", "add" or "remove"\n' + % args[0]) if (len(args) == 2 and args[0] not in ['add', 'remove']): - parser.error('unknown command %r. Try "list", "add" or "remove"\n' % args[0]) + parser.error('unknown command %r. Try "list", "add" or "remove"\n' + % args[0]) if not options.login: parser.error('you must provide --login information\n') @@ -53,7 +78,7 @@ def parse_commandline(): def main(): - """This implements the actual program functionality""" + """Implement the actual program functionality.""" options, args = parse_commandline() @@ -71,20 +96,23 @@ def main(): datefmt="%Y-%m-%dT%H:%M:%S") if len(args) == 1: for repos in github.repos.list(options.account): - fullreposname = github.project_for_user_repo(options.account, repos.name) - print "%s: %s" % (repos.name, ' '.join(github.repos.list_collaborators(fullreposname))) + fullreposname = github.project_for_user_repo(options.account, + repos.name) + collabs = github.repos.list_collaborators(fullreposname) + print_("%s: %s" % (repos.name, ' '.join(collabs))) elif len(args) == 2: command, collaborator = args for repos in github.repos.list(options.account): - fullreposname = github.project_for_user_repo(options.account, repos.name) + fullreposname = github.project_for_user_repo(options.account, + repos.name) if collaborator in github.repos.list_collaborators(fullreposname): if command == 'remove': github.repos.remove_collaborator(repos.name, collaborator) - print "removed %r from %r" % (collaborator, repos.name) + print_("removed %r from %r" % (collaborator, repos.name)) else: if command == 'add': github.repos.add_collaborator(repos.name, collaborator) - print "added %r to %r" % (collaborator, repos.name) + print_("added %r to %r" % (collaborator, repos.name)) logging.shutdown() diff --git a/github2/bin/search_repos.py b/github2/bin/search_repos.py index 75b295c..1023e2c 100755 --- a/github2/bin/search_repos.py +++ b/github2/bin/search_repos.py @@ -1,6 +1,10 @@ #! /usr/bin/env python # coding: utf-8 """github_search_repos - search for repositories on GitHub""" +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. import logging @@ -12,8 +16,24 @@ import github2.client +#: Running under Python 3 +PY3K = sys.version_info[0] == 3 and True or False + + +def print_(text): + """Python 2 & 3 compatible print function. + + We support <2.6, so can't use __future__.print_function + + """ + if PY3K: + print(text) + else: + sys.stdout.write(text + '\n') + + def parse_commandline(): - """Parse the comandline and return parsed options.""" + """Parse the command line and return parsed options.""" parser = OptionParser() parser.description = __doc__ @@ -32,7 +52,7 @@ def parse_commandline(): def main(): - """This implements the actual program functionality""" + """Implement the actual program functionality.""" return_value = 0 options, term = parse_commandline() @@ -47,14 +67,14 @@ def main(): repos = github.repos.search(term) if not repos: - print 'No repos found!' + print_('No repos found!') return_value = 255 else: for repo in repos: - print repo.project + print(repo.project) if repo.description: - print '\n'.join(wrap(repo.description, initial_indent=' ', - subsequent_indent=' ')) + print_('\n'.join(wrap(repo.description, initial_indent=' ', + subsequent_indent=' '))) logging.shutdown() return return_value diff --git a/github2/client.py b/github2/client.py index a26210d..d0a3f2b 100644 --- a/github2/client.py +++ b/github2/client.py @@ -1,3 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2009-2012 Ask Solem +# Christopher MacGown +# Evan Broder +# James Rowe +# Jeremy Dunck +# Michael Basnight +# Patryk Zawadzki +# Surajram Kumaravel +# Vincent Driessen +# modocache +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + from github2.request import GithubRequest from github2.issues import Issues from github2.repositories import Repositories @@ -10,12 +25,12 @@ class Github(object): + """Interface to GitHub's API v2.""" + def __init__(self, username=None, api_token=None, requests_per_second=None, access_token=None, cache=None, proxy_host=None, - proxy_port=8080): - """ - An interface to GitHub's API: - http://develop.github.com/ + proxy_port=8080, github_url=None): + """Setup GitHub API object. .. versionadded:: 0.2.0 The ``requests_per_second`` parameter @@ -23,6 +38,8 @@ def __init__(self, username=None, api_token=None, requests_per_second=None, The ``cache`` and ``access_token`` parameters .. versionadded:: 0.4.0 The ``proxy_host`` and ``proxy_port`` parameters + .. versionadded:: 0.7.0 + The ``github_url`` parameter :param str username: your own GitHub username. :param str api_token: can be found at https://github.com/account @@ -38,13 +55,17 @@ def __init__(self, username=None, api_token=None, requests_per_second=None, :param str proxy_host: the hostname for the HTTP proxy, if needed. :param str proxy_port: the hostname for the HTTP proxy, if needed (will default to 8080 if a proxy_host is set and no port is set). + :param str github_url: the hostname to connect to, for GitHub + Enterprise support + """ self.request = GithubRequest(username=username, api_token=api_token, requests_per_second=requests_per_second, access_token=access_token, cache=cache, proxy_host=proxy_host, - proxy_port=proxy_port) + proxy_port=proxy_port, + github_url=github_url) self.issues = Issues(self.request) self.users = Users(self.request) self.repos = Repositories(self.request) @@ -54,59 +75,65 @@ def __init__(self, username=None, api_token=None, requests_per_second=None, self.pull_requests = PullRequests(self.request) def project_for_user_repo(self, user, repo): - """Return Github identifier for a user's repository + """Return GitHub identifier for a user's repository. :param str user: repository owner :param str repo: repository name + """ return "/".join([user, repo]) def get_all_blobs(self, project, tree_sha): - """Get a list of all blobs for a specific tree + """Get a list of all blobs for a specific tree. .. versionadded:: 0.3.0 :param str project: GitHub project :param str tree_sha: object ID of tree + """ blobs = self.request.get("blob/all", project, tree_sha) return blobs.get("blobs") def get_blob_info(self, project, tree_sha, path): - """Get the blob for a file within a specific tree + """Get the blob for a file within a specific tree. :param str project: GitHub project :param str tree_sha: object ID of tree :param str path: path within tree to fetch blob for + """ blob = self.request.get("blob/show", project, tree_sha, path) return blob.get("blob") def get_tree(self, project, tree_sha): - """Get tree information for a specifc tree + """Get tree information for a specific tree. :param str project: GitHub project :param str tree_sha: object ID of tree + """ tree = self.request.get("tree/show", project, tree_sha) return tree.get("tree", []) def get_network_meta(self, project): - """Get Github metadata associated with a project + """Get GitHub metadata associated with a project. :param str project: GitHub project + """ return self.request.raw_request("/".join([self.request.github_url, project, "network_meta"]), {}) def get_network_data(self, project, nethash, start=None, end=None): - """Get chunk of Github network data + """Get chunk of GitHub network data. :param str project: GitHub project - :param str nethash: identifier provided by ``get_network_meta`` + :param str nethash: identifier provided by :meth:`get_network_meta` :param int start: optional start point for data :param int stop: optional end point for data + """ data = {"nethash": nethash} if start: diff --git a/github2/commits.py b/github2/commits.py index 8910937..40e6a8e 100644 --- a/github2/commits.py +++ b/github2/commits.py @@ -1,8 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2009-2012 Ask Solem +# James Rowe +# Stéphane Angel +# Vincent Driessen +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + from github2.core import (BaseData, GithubCommand, Attribute, DateAttribute, repr_string) class Commit(BaseData): + + """Commit container.""" + message = Attribute("Commit message.") parents = Attribute("List of parents for this commit.") url = Attribute("Canonical URL for this commit.") @@ -25,10 +37,13 @@ def __repr__(self): class Commits(GithubCommand): + + """GitHub API commits functionality.""" + domain = "commits" def list(self, project, branch="master", file=None, page=1): - """List commits on a project + """List commits on a project. .. warning:: Not all projects use ``master`` as their default branch, you can @@ -39,15 +54,17 @@ def list(self, project, branch="master", file=None, page=1): :param str branch: branch name, or ``master`` if not given :param str file: optional file filter :param int page: optional page number + """ return self.get_values("list", project, branch, file, filter="commits", datatype=Commit, page=page) def show(self, project, sha): - """Get a specific commit + """Get a specific commit. :param str project: project name :param str sha: commit id + """ return self.get_value("show", project, sha, filter="commit", datatype=Commit) diff --git a/github2/core.py b/github2/core.py index 4e11ec7..86d4788 100644 --- a/github2/core.py +++ b/github2/core.py @@ -1,13 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2009-2012 Ask Solem +# Fernando Perez +# James Rowe +# Mark Paschal +# Patryk Zawadzki +# Sameer Al-Sakran +# Stéphane Angel +# Vincent Driessen +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + +import logging import sys -from warnings import warn - from datetime import datetime from dateutil import (parser, tz) +#: Logger for core module +LOGGER = logging.getLogger('github2.core') + #: Running under Python 3 -PY3K = sys.version_info[0] == 3 and True or False +PY3K = sys.version_info[0] == 3 + +#: Running under Python 2.7, or newer +PY27 = sys.version_info[:2] >= (2, 7) GITHUB_DATE_FORMAT = "%Y/%m/%d %H:%M:%S %z" # We need to manually mangle the timezone for commit date formatting because it @@ -22,9 +40,10 @@ def string_to_datetime(string): - """Convert a string to Python datetime + """Convert a string to Python datetime. :param str github_date: date string to parse + """ parsed = parser.parse(string) if NAIVE: @@ -33,9 +52,10 @@ def string_to_datetime(string): def _handle_naive_datetimes(f): - """Decorator to make datetime arguments use GitHub timezone + """Decorator to make datetime arguments use GitHub timezone. :param func f: Function to wrap + """ def wrapper(datetime_): if not datetime_.tzinfo: @@ -54,18 +74,20 @@ def wrapper(datetime_): @_handle_naive_datetimes def datetime_to_ghdate(datetime_): - """Convert Python datetime to Github date string + """Convert Python datetime to GitHub date string. :param datetime datetime_: datetime object to convert + """ return datetime_.strftime(GITHUB_DATE_FORMAT) @_handle_naive_datetimes def datetime_to_commitdate(datetime_): - """Convert Python datetime to Github date string + """Convert Python datetime to GitHub date string. :param datetime datetime_: datetime object to convert + """ date_without_tz = datetime_.strftime(COMMIT_DATE_FORMAT) utcoffset = GITHUB_TZ.utcoffset(datetime_) @@ -75,7 +97,7 @@ def datetime_to_commitdate(datetime_): def datetime_to_isodate(datetime_): - """Convert Python datetime to Github date string + """Convert Python datetime to GitHub date string. :param str datetime_: datetime object to convert @@ -89,38 +111,43 @@ def datetime_to_isodate(datetime_): class AuthError(Exception): - """Requires authentication""" + + """Requires authentication.""" def requires_auth(f): - """Decorate to check a function call for authentication + """Decorate to check a function call for authentication. Sets a ``requires_auth`` attribute on functions, for use in introspection. :param func f: Function to wrap :raises AuthError: If function called without an authenticated session + """ - # When Python 2.4 support is dropped move straight to functools.wraps, don't - # pass go and don't collect $200. + # When Python 2.4 support is dropped move straight to functools.wraps, + # don't pass go and don't collect $200. def wrapper(self, *args, **kwargs): if not self.request.access_token and not self.request.api_token: raise AuthError("%r requires an authenticated session" % f.__name__) return f(self, *args, **kwargs) wrapped = wrapper + wrapped.__orig_func__ = f wrapped.__name__ = f.__name__ wrapped.__doc__ = f.__doc__ + """\n.. warning:: Requires authentication""" + wrapped.__module__ = f.__module__ wrapped.requires_auth = True return wrapped def enhanced_by_auth(f): - """Decorator to mark a function as enhanced by authentication + """Decorator to mark a function as enhanced by authentication. Sets a ``enhanced_by_auth`` attribute on functions, for use in introspection. :param func f: Function to wrap + """ f.enhanced_by_auth = True f.__doc__ += """\n.. note:: This call is enhanced with authentication""" @@ -129,23 +156,42 @@ def enhanced_by_auth(f): class GithubCommand(object): + """Main API binding interface.""" + def __init__(self, request): + """Setup command object. + + :param github2.request.GithubRequest request: HTTP request handler + + """ self.request = request def make_request(self, command, *args, **kwargs): + """Make an API request. + + Various options are supported if they exist in ``kwargs``: + + * The value of a ``method`` argument will define the HTTP method + to perform for this request, the default is ``GET`` + * The value of a ``filter`` argument will restrict the response to that + data + * The value of a ``page`` argument will be used to fetch a specific + page of results, default of 1 is assumed if not given + + """ filter = kwargs.get("filter") post_data = kwargs.get("post_data") or {} page = kwargs.pop("page", 1) if page and not page == 1: post_data["page"] = page - method = kwargs.get("method", "GET") - if method.upper() == "POST" or method.upper() == "GET" and post_data: + method = kwargs.get("method", "GET").upper() + if method == "POST" or method == "GET" and post_data: response = self.request.post(self.domain, command, *args, **post_data) - elif method.upper() == "PUT": + elif method == "PUT": response = self.request.put(self.domain, command, *args, **post_data) - elif method.upper() == "DELETE": + elif method == "DELETE": response = self.request.delete(self.domain, command, *args, **post_data) else: @@ -155,32 +201,52 @@ def make_request(self, command, *args, **kwargs): return response def get_value(self, *args, **kwargs): + """Process a single-value response from the API. + + If a ``datatype`` parameter is given it defines the + :class:`BaseData`-derived class we should build from the provided data + + """ datatype = kwargs.pop("datatype", None) value = self.make_request(*args, **kwargs) if datatype: - # unicode keys are not accepted as kwargs by python, see: - #http://mail-archives.apache.org/mod_mbox/qpid-dev/200609.mbox/%3C1159389941.4505.10.camel@localhost.localdomain%3E - # So we make a local dict with the same keys but as strings: - return datatype(**dict((str(k), v) for (k, v) in value.iteritems())) + if not PY27: + # unicode keys are not accepted as kwargs by python, until 2.7: + # http://bugs.python.org/issue2646 + # So we make a local dict with the same keys but as strings: + return datatype(**dict((str(k), v) + for (k, v) in value.items())) + else: + return datatype(**value) return value def get_values(self, *args, **kwargs): + """Process a multi-value response from the API. + + :see: :meth:`get_value` + + """ datatype = kwargs.pop("datatype", None) values = self.make_request(*args, **kwargs) if datatype: - # Same as above, unicode keys will blow up in **args, so we need to - # create a new 'values' dict with string keys - return [datatype(**dict((str(k), v) for (k, v) in value.iteritems())) - for value in values] + if not PY27: + # Same as above, unicode keys will blow up in **args, so we + # need to create a new 'values' dict with string keys + return [datatype(**dict((str(k), v) + for (k, v) in value.items())) + for value in values] + else: + return [datatype(**value) for value in values] else: return values def doc_generator(docstring, attributes): - """Utility function to augment BaseDataType docstring + """Utility function to augment BaseDataType docstring. :param str docstring: docstring to augment :param dict attributes: attributes to add to docstring + """ docstring = docstring or "" @@ -194,7 +260,14 @@ def bullet(title, text): class Attribute(object): + """Generic object attribute for use with :class:`BaseData`.""" + def __init__(self, help): + """Setup Attribute object. + + :param str help: Attribute description + + """ self.help = help def to_python(self, value): @@ -204,6 +277,9 @@ def to_python(self, value): class DateAttribute(Attribute): + + """Date handling attribute for use with :class:`BaseData`.""" + format = "github" converter_for_format = { "github": datetime_to_ghdate, @@ -213,6 +289,12 @@ class DateAttribute(Attribute): } def __init__(self, *args, **kwargs): + """Setup DateAttribute object. + + :param str format: The date format to support, see + :data:`convertor_for_format` for supported options + + """ self.format = kwargs.pop("format", self.format) super(DateAttribute, self).__init__(*args, **kwargs) @@ -233,15 +315,14 @@ def __new__(cls, name, bases, attrs): super_new = super(BaseDataType, cls).__new__ _meta = dict([(attr_name, attr_value) - for attr_name, attr_value in attrs.items() - if isinstance(attr_value, Attribute)]) + for attr_name, attr_value in attrs.items() + if isinstance(attr_value, Attribute)]) attrs["_meta"] = _meta attributes = _meta.keys() - attrs.update(dict([(attr_name, None) - for attr_name in attributes])) + attrs.update(dict([(attr_name, None) for attr_name in attributes])) def _contribute_method(name, func): - func.func_name = name + func.__name__ = name attrs[name] = func def constructor(self, **kwargs): @@ -263,38 +344,50 @@ def iterate(self): return result_cls -class BaseData(object): - __metaclass__ = BaseDataType +# Ugly base class definition for Python 2 and 3 compatibility, where metaclass +# syntax is incompatible +class BaseData(BaseDataType('BaseData', (object, ), {})): + + """Wrapper for API responses. + + .. warning:: + Supports subscript attribute access purely for backwards compatibility, + you shouldn't rely on that functionality in new code + + """ def __getitem__(self, key): - """Access objects's attribute using subscript notation + """Access objects's attribute using subscript notation. This is here purely to maintain compatibility when switching ``dict`` responses to ``BaseData`` derived objects. + """ - warn("Subscript access on %r is deprecated, use object attributes" - % self.__class__.__name__, DeprecationWarning) + LOGGER.warning("Subscript access on %r is deprecated, use object " + "attributes" % self.__class__.__name__) if not key in self._meta.keys(): raise KeyError(key) return getattr(self, key) def __setitem__(self, key, value): - """Update object's attribute using subscript notation + """Update object's attribute using subscript notation. + + :see: :meth:`BaseData.__getitem__` - :see: ``BaseData.__getitem__`` """ - warn("Subscript access on %r is deprecated, use object attributes" - % self.__class__.__name__, DeprecationWarning) + LOGGER.warning("Subscript access on %r is deprecated, use object " + "attributes" % self.__class__.__name__) if not key in self._meta.keys(): raise KeyError(key) setattr(self, key, value) def repr_string(string): - """Shorten string for use in repr() output + """Shorten string for use in repr() output. :param str string: string to operate on :return: string, with maximum length of 20 characters + """ if len(string) > 20: string = string[:17] + '...' diff --git a/github2/issues.py b/github2/issues.py index 43111b0..fa87098 100644 --- a/github2/issues.py +++ b/github2/issues.py @@ -1,10 +1,26 @@ -import urllib +# Copyright (C) 2009-2012 Adam Vandenberg +# Ask Solem +# Barthelemy Dagenais +# Fernando Perez +# James Rowe +# Scott Torborg +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + +try: + from urllib.parse import quote_plus # For Python 3 +except ImportError: + from urllib import quote_plus # NOQA from github2.core import (GithubCommand, BaseData, Attribute, DateAttribute, repr_string, requires_auth) class Issue(BaseData): + + """Issue container.""" + position = Attribute("The position of this issue in a list.") number = Attribute("The issue number (unique for project).") votes = Attribute("Number of votes for this issue.") @@ -25,6 +41,9 @@ def __repr__(self): class Comment(BaseData): + + """Comment container.""" + created_at = DateAttribute("The date this comment was created.") updated_at = DateAttribute("The date when this comment was last updated.") body = Attribute("The full text of this comment.") @@ -36,6 +55,9 @@ def __repr__(self): class Issues(GithubCommand): + + """GitHub API issues functionality.""" + domain = "issues" def search(self, project, term, state="open"): @@ -45,17 +67,18 @@ def search(self, project, term, state="open"): :param str project: GitHub project :param str term: term to search issues for - :param str state: can be either ``open`` or ``closed``. + :param str state: can be either ``open`` or ``closed`` + """ - return self.get_values("search", project, state, - urllib.quote_plus(term), filter="issues", - datatype=Issue) + return self.get_values("search", project, state, quote_plus(term), + filter="issues", datatype=Issue) def list(self, project, state="open"): """Get all issues for project with given state. :param str project: GitHub project - :param str state: can be either ``open`` or ``closed``. + :param str state: can be either ``open`` or ``closed`` + """ return self.get_values("list", project, state, filter="issues", datatype=Issue) @@ -66,10 +89,11 @@ def list_by_label(self, project, label): .. versionadded:: 0.3.0 :param str project: GitHub project - :param str label: a string representing a label (e.g., ``bug``). + :param str label: a string representing a label (e.g., ``bug``) + """ - return self.get_values("list", project, "label", label, filter="issues", - datatype=Issue) + return self.get_values("list", project, "label", label, + filter="issues", datatype=Issue) def list_labels(self, project): """Get all labels for project. @@ -77,6 +101,7 @@ def list_labels(self, project): .. versionadded:: 0.3.0 :param str project: GitHub project + """ return self.get_values("labels", project, filter="labels") @@ -84,7 +109,8 @@ def show(self, project, number): """Get all the data for issue by issue-number. :param str project: GitHub project - :param int number: issue number in the Github database + :param int number: issue number in the GitHub database + """ return self.get_value("show", project, str(number), filter="issue", datatype=Issue) @@ -96,6 +122,7 @@ def open(self, project, title, body): :param str project: GitHub project :param str title: title for issue :param str body: body for issue + """ issue_data = {"title": title, "body": body} return self.get_value("open", project, post_data=issue_data, @@ -103,36 +130,39 @@ def open(self, project, title, body): @requires_auth def close(self, project, number): - """Close an issue + """Close an issue. :param str project: GitHub project - :param int number: issue number in the Github database + :param int number: issue number in the GitHub database + """ return self.get_value("close", project, str(number), filter="issue", datatype=Issue, method="POST") @requires_auth def reopen(self, project, number): - """Reopen a closed issue + """Reopen a closed issue. .. versionadded:: 0.3.0 :param str project: GitHub project - :param int number: issue number in the Github database + :param int number: issue number in the GitHub database + """ return self.get_value("reopen", project, str(number), filter="issue", datatype=Issue, method="POST") @requires_auth def edit(self, project, number, title, body): - """Edit an existing issue + """Edit an existing issue. .. versionadded:: 0.3.0 :param str project: GitHub project - :param int number: issue number in the Github database + :param int number: issue number in the GitHub database :param str title: title for issue :param str body: body for issue + """ issue_data = {"title": title, "body": body} return self.get_value("edit", project, str(number), @@ -141,22 +171,24 @@ def edit(self, project, number, title, body): @requires_auth def add_label(self, project, number, label): - """Add a label to an issue + """Add a label to an issue. :param str project: GitHub project - :param int number: issue number in the Github database + :param int number: issue number in the GitHub database :param str label: label to attach to issue + """ return self.get_values("label/add", project, label, str(number), filter="labels", method="POST") @requires_auth def remove_label(self, project, number, label): - """Remove an existing label from an issue + """Remove an existing label from an issue. :param str project: GitHub project - :param int number: issue number in the Github database + :param int number: issue number in the GitHub database :param str label: label to remove from issue + """ return self.get_values("label/remove", project, label, str(number), filter="labels", method="POST") @@ -166,8 +198,9 @@ def comment(self, project, number, comment): """Comment on an issue. :param str project: GitHub project - :param int number: issue number in the Github database + :param int number: issue number in the GitHub database :param str comment: comment to attach to issue + """ comment_data = {'comment': comment} return self.get_value("comment", project, str(number), @@ -178,7 +211,7 @@ def comments(self, project, number): """View comments on an issue. :param str project: GitHub project - :param int number: issue number in the Github database + """ return self.get_values("comments", project, str(number), filter="comments", datatype=Comment) diff --git a/github2/organizations.py b/github2/organizations.py index 25df990..d11f9c4 100644 --- a/github2/organizations.py +++ b/github2/organizations.py @@ -1,11 +1,25 @@ -from github2.core import BaseData, GithubCommand, Attribute, DateAttribute +# Copyright (C) 2011-2012 James Rowe +# Patryk Zawadzki +# Rok Garbas +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + +from github2.core import (BaseData, GithubCommand, Attribute, DateAttribute, + requires_auth) from github2.repositories import Repository from github2.teams import Team from github2.users import User class Organization(BaseData): - """.. versionadded:: 0.4.0""" + + """Organization container. + + .. versionadded:: 0.4.0 + + """ + id = Attribute("The organization id.") name = Attribute("The full name of the organization.") blog = Attribute("The organization's blog.") @@ -16,10 +30,13 @@ class Organization(BaseData): company = Attribute("The organization's company name.") created_at = DateAttribute("The date the organization was created.", format="commit") - following_count = Attribute("Number of users the organization is following.") + following_count = Attribute("Number of users the organization is " + "following.") followers_count = Attribute("Number of users following this organization.") - public_gist_count = Attribute("Organization's number of active public gists.") - public_repo_count = Attribute("Organization's number of active repositories.") + public_gist_count = Attribute("Organization's number of active public " + "gists.") + public_repo_count = Attribute("Organization's number of active " + "repositories.") permission = Attribute("Permissions within this organization.") plan = Attribute("GitHub plan for this organization.") @@ -31,53 +48,80 @@ def __repr__(self): class Organizations(GithubCommand): - """.. versionadded:: 0.4.0""" + + """GitHub API organizations functionality. + + .. versionadded:: 0.4.0 + + """ + domain = "organizations" def show(self, organization): - """Get information on organization + """Get information on organization. :param str organization: organization to show + """ return self.get_value(organization, filter="organization", datatype=Organization) def list(self): - """Get list of all of your organizations""" + """Get list of all of your organizations.""" return self.get_values('', filter="organizations", datatype=Organization) def repositories(self, organization=''): - """Get list of all repositories in an organization + """Get list of all repositories in an organization. If organization is not given, or is empty, then this will list repositories for all organizations the authenticated user belongs to. :param: str organization: organization to list repositories for + """ return self.get_values(organization, 'repositories', filter="repositories", datatype=Repository) def public_repositories(self, organization): - """Get list of public repositories in an organization + """Get list of public repositories in an organization. :param str organization: organization to list public repositories for + """ return self.get_values(organization, 'public_repositories', filter="repositories", datatype=Repository) def public_members(self, organization): - """Get list of public members in an organization + """Get list of public members in an organization. :param str organization: organization to list members for + """ return self.get_values(organization, 'public_members', filter="users", datatype=User) def teams(self, organization): - """Get list of teams in an organization + """Get list of teams in an organization. :param str organization: organization to list teams for + """ return self.get_values(organization, 'teams', filter="teams", datatype=Team) + + @requires_auth + def add_team(self, organization, name, permission='pull', projects=None): + """Add a team to an organization. + + :param str organization: organization to add team to + :param str team: name of team to add + :param str permission: permissions for team(push, pull or admin) + :param list projects: optional GitHub projects for this team + + """ + team_data = {'team[name]': name, 'team[permission]': permission} + if projects: + team_data['team[repo_names][]'] = projects + return self.get_value(organization, 'teams', post_data=team_data, + method='POST', filter='team', datatype=Team) diff --git a/github2/pull_requests.py b/github2/pull_requests.py index 3573f75..d4ea6e9 100644 --- a/github2/pull_requests.py +++ b/github2/pull_requests.py @@ -1,12 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 Christopher MacGown +# Ionuț Arțăriși +# James Rowe +# Stéphane Angel +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + from github2.core import (BaseData, GithubCommand, Attribute, DateAttribute, repr_string) class PullRequest(BaseData): - """Pull request encapsulation + + """Pull request container. .. versionadded:: 0.5.0 + """ + state = Attribute("The pull request state") base = Attribute("The base repo") head = Attribute("The head of the pull request") @@ -22,14 +34,14 @@ class PullRequest(BaseData): patch_url = Attribute("The URL to the downloadable patch.") labels = Attribute("A list of labels attached to the pull request.") html_url = Attribute("The URL to the pull request.") - issue_created_at = DateAttribute("The date the issue for this pull request was opened.", - format='iso') - issue_updated_at = DateAttribute("The date the issue for this pull request was last updated.", - format='iso') + issue_created_at = DateAttribute("The date the issue for this pull " + "request was opened.", format='iso') + issue_updated_at = DateAttribute("The date the issue for this pull " + "request was last updated.", format='iso') created_at = DateAttribute("The date when this pull request was created.", format='iso') - updated_at = DateAttribute("The date when this pull request was last updated.", - format='iso') + updated_at = DateAttribute("The date when this pull request was last " + "updated.", format='iso') closed_at = DateAttribute("The date when this pull request was closed", format='iso') discussion = Attribute("Discussion thread for the pull request.") @@ -40,25 +52,29 @@ def __repr__(self): class PullRequests(GithubCommand): - """Operations on pull requests + + """GitHub API pull request functionality. .. versionadded:: 0.5.0 + """ + domain = "pulls" def create(self, project, base, head, title=None, body=None, issue=None): - """Create a new pull request + """Create a new pull request. Pull requests can be created from scratch, or attached to an existing issue. If an ``issue`` parameter is supplied the pull request is attached to that issue, else a new pull request is created. - :param str project: the Github project to send the pull request to + :param str project: the GitHub project to send the pull request to :param str base: branch changes should be pulled into :param str head: branch of the changes to be pulled :param str title: title for pull request :param str body: optional body for pull request :param str issue: existing issue to attach pull request to + """ post_data = {"base": base, "head": head} if issue: @@ -76,20 +92,22 @@ def create(self, project, base, head, title=None, body=None, issue=None): filter="pull", datatype=PullRequest) def show(self, project, number): - """Show a single pull request + """Show a single pull request. + + :param str project: GitHub project + :param int number: pull request number in the GitHub database - :param str project: Github project - :param int number: pull request number in the Github database """ return self.get_value(project, str(number), filter="pull", datatype=PullRequest) def list(self, project, state="open", page=1): - """List all pull requests for a project + """List all pull requests for a project. - :param str project: Github project + :param str project: GitHub project :param str state: can be either ``open`` or ``closed`` :param int page: optional page number + """ return self.get_values(project, state, filter="pulls", datatype=PullRequest, page=page) diff --git a/github2/repositories.py b/github2/repositories.py index 2724627..bb84bcf 100644 --- a/github2/repositories.py +++ b/github2/repositories.py @@ -1,3 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2009-2012 Ask Solem +# Claudio B. +# Daniel Greenfeld +# James Rowe +# Jens Ohlig +# Jeremy Dunck +# Jonas Obrist +# Kenneth Reitz +# Mark Paschal +# Maximillian Dornseif +# Sameer Al-Sakran +# Stéphane Angel +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + from github2.core import (BaseData, GithubCommand, Attribute, DateAttribute, requires_auth) @@ -5,6 +22,9 @@ class Repository(BaseData): + + """Repository container.""" + name = Attribute("Name of repository.") description = Attribute("Repository description.") forks = Attribute("Number of forks of this repository.") @@ -14,7 +34,7 @@ class Repository(BaseData): fork = Attribute("If True, this is a fork of another repository.") owner = Attribute("Username of the user owning this repository.") homepage = Attribute("Homepage for this project.") - master_branch = Attribute("Default branch, if set.") + master_branch = Attribute("Default branch, if set.") integration_branch = Attribute("Integration branch, if set.") open_issues = Attribute("List of open issues for this repository.") created_at = DateAttribute("Datetime the repository was created.") @@ -24,6 +44,7 @@ class Repository(BaseData): has_issues = Attribute("If True, this repository has an issue tracker.") language = Attribute("Primary language for the repository.") parent = Attribute("The parent project of this fork.") + source = Attribute("The root project of this fork") def _project(self): return self.owner + "/" + self.name @@ -34,6 +55,8 @@ def __repr__(self): class Repositories(GithubCommand): + """GitHub API repository functionality.""" + domain = "repos" def search(self, query): @@ -43,6 +66,7 @@ def search(self, query): Returns at most 100 repositories :param str query: term to search issues for + """ return self.get_values("search", query, filter="repositories", datatype=Repository) @@ -51,6 +75,7 @@ def show(self, project): """Get repository object for project. :param str project: GitHub project + """ return self.get_value("show", project, filter="repository", datatype=Repository) @@ -60,21 +85,22 @@ def pushable(self): """Return a list of repos you can push to that are not your own. .. versionadded:: 0.3.0 + """ return self.get_values("pushable", filter="repositories", datatype=Repository) - def list(self, user=None, page=1): """Return a list of all repositories for a user. - .. deprecated: 0.4.0 + .. deprecated:: 0.4.0 Previous releases would attempt to display repositories for the logged-in user when ``user`` wasn't supplied. This functionality is brittle and will be removed in a future release! - :param str user: Github user name to list repositories for + :param str user: GitHub user name to list repositories for :param int page: optional page number + """ user = user or self.request.username return self.get_values("show", user, filter="repositories", @@ -82,39 +108,43 @@ def list(self, user=None, page=1): @requires_auth def watch(self, project): - """Watch a project + """Watch a project. :param str project: GitHub project + """ return self.get_value("watch", project, filter='repository', datatype=Repository) @requires_auth def unwatch(self, project): - """Unwatch a project + """Unwatch a project. :param str project: GitHub project + """ return self.get_value("unwatch", project, filter='repository', datatype=Repository) @requires_auth def fork(self, project): - """Fork a project + """Fork a project. :param str project: GitHub project + """ return self.get_value("fork", project, filter="repository", datatype=Repository) @requires_auth def create(self, project, description=None, homepage=None, public=True): - """Create a repository + """Create a repository. :param str project: new project name :param str description: optional project description :param str homepage: optional project homepage :param bool public: whether to make a public project + """ repo_data = {"name": project, "description": description, "homepage": homepage, "public": str(int(public))} @@ -123,9 +153,10 @@ def create(self, project, description=None, homepage=None, public=True): @requires_auth def delete(self, project): - """Delete a repository + """Delete a repository. :param str project: project name to delete + """ # Two-step delete mechanism. We must echo the delete_token value back # to GitHub to actually delete a repository @@ -134,98 +165,111 @@ def delete(self, project): @requires_auth def set_private(self, project): - """Mark repository as private + """Mark repository as private. :param str project: project name to set as private + """ return self.make_request("set/private", project) @requires_auth def set_public(self, project): - """Mark repository as public + """Mark repository as public. :param str project: project name to set as public + """ return self.make_request("set/public", project) def list_collaborators(self, project): - """Lists all the collaborators in a project + """List all the collaborators in a project. :param str project: GitHub project + """ return self.get_values("show", project, "collaborators", filter="collaborators") @requires_auth def add_collaborator(self, project, username): - """Adds an add_collaborator to a repo + """Add an add_collaborator to a repo. + + :param str project: GitHub project + :param str username: GitHub user to add as collaborator - :param str project: Github project - :param str username: Github user to add as collaborator """ return self.make_request("collaborators", project, "add", username, method="POST") @requires_auth def remove_collaborator(self, project, username): - """Removes an add_collaborator from a repo + """Remove a collaborator from a repo. + + :param str project: GitHub project + :param str username: GitHub user to add as collaborator - :param str project: Github project - :param str username: Github user to add as collaborator """ return self.make_request("collaborators", project, "remove", username, method="POST") def network(self, project): - """Get network data for project + """Get network data for project. + + :param str project: GitHub project - :param str project: Github project """ return self.get_values("show", project, "network", filter="network", datatype=Repository) def languages(self, project): - """Get programming language data for project + """Get programming language data for project. + + :param str project: GitHub project - :param str project: Github project """ - return self.get_values("show", project, "languages", filter="languages") + return self.get_values("show", project, "languages", + filter="languages") def tags(self, project): - """Get tags for project + """Get tags for project. + + :param str project: GitHub project - :param str project: Github project """ return self.get_values("show", project, "tags", filter="tags") def branches(self, project): - """Get branch names for project + """Get branch names for project. + + :param str project: GitHub project - :param str project: Github project """ return self.get_values("show", project, "branches", filter="branches") def watchers(self, project): - """Get list of watchers for project + """Get list of watchers for project. + + :param str project: GitHub project - :param str project: Github project """ return self.get_values("show", project, "watchers", filter="watchers") def watching(self, for_user=None, page=None): - """Lists all the repos a user is watching + """List all the repos a user is watching. - :param str for_user: optional Github user name to list repositories for + :param str for_user: optional GitHub user name to list repositories for :param int page: optional page number + """ for_user = for_user or self.request.username return self.get_values("watched", for_user, filter="repositories", datatype=Repository, page=page) def list_contributors(self, project): - """Lists all the contributors in a project + """List all the contributors in a project. + + :param str project: GitHub project - :param str project: Github project """ return self.get_values("show", project, "contributors", filter="contributors", datatype=User) diff --git a/github2/request.py b/github2/request.py index d350d55..5961471 100644 --- a/github2/request.py +++ b/github2/request.py @@ -1,34 +1,102 @@ +# Copyright (C) 2009-2012 Adam Vandenberg +# Asheesh Laroia +# Ask Solem +# Chris Vale +# Daniel Greenfeld +# Evan Broder +# James Rowe +# Jeremy Dunck +# Josh Weinberg +# Mark Paschal +# Maximillian Dornseif +# Michael Basnight +# Patryk Zawadzki +# Rick Harris +# Sameer Al-Sakran +# Vincent Driessen +# modocache +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + import datetime import logging import re +import sys import time -import httplib2 -from httplib import responses + try: - import json as simplejson # For Python 2.6 + # For Python 3 + from http.client import responses +except ImportError: # For Python 2.5-2.7 + try: + from httplib import responses # NOQA + except ImportError: # For Python 2.4 + from BaseHTTPServer import BaseHTTPRequestHandler as _BHRH + responses = dict([(k, v[0]) for k, v in _BHRH.responses.items()]) # NOQA +try: + import json as simplejson # For Python 2.6+ except ImportError: - import simplejson -from os import path -from urlparse import (urlsplit, urlunsplit) + import simplejson # NOQA +from os import (getenv, path) try: - from urlparse import parse_qs + # For Python 3 + from urllib.parse import (parse_qs, quote, urlencode, urlsplit, urlunsplit) except ImportError: - from cgi import parse_qs -from urllib import urlencode, quote + from urlparse import (urlsplit, urlunsplit) # NOQA + try: + from urlparse import parse_qs # NOQA + except ImportError: + from cgi import parse_qs # NOQA + from urllib import urlencode, quote # NOQA + +import httplib2 #: Hostname for API access -GITHUB_URL = "https://github.com" +DEFAULT_GITHUB_URL = "https://github.com" #: Logger for requests module LOGGER = logging.getLogger('github2.request') +# Fetch actual path for httplib2's default cert bundle, for distributions that +# symlink their system certs +_HTTPLIB2_BUNDLE = path.realpath(path.dirname(httplib2.CA_CERTS)) +#: Whether github2 is using the system's certificates for SSL connections +SYSTEM_CERTS = not _HTTPLIB2_BUNDLE.startswith(path.dirname(httplib2.__file__)) +CA_CERTS = None +#: Whether github2 is using the cert's from the file given in $CURL_CA_BUNDLE +CURL_CERTS = False +if not SYSTEM_CERTS and sys.platform.startswith('linux'): + for cert_file in ['/etc/ssl/certs/ca-certificates.crt', + '/etc/pki/tls/certs/ca-bundle.crt']: + if path.exists(cert_file): + CA_CERTS = cert_file + SYSTEM_CERTS = True + break +elif not SYSTEM_CERTS and sys.platform.startswith('freebsd'): + if path.exists('/usr/local/share/certs/ca-root-nss.crt'): + CA_CERTS = '/usr/local/share/certs/ca-root-nss.crt' + SYSTEM_CERTS = True +elif path.exists(getenv('CURL_CA_BUNDLE', '')): + CA_CERTS = getenv('CURL_CA_BUNDLE') + CURL_CERTS = True +if not SYSTEM_CERTS and not CURL_CERTS: + CA_CERTS = path.join(path.dirname(path.abspath(__file__)), + "DigiCert_High_Assurance_EV_Root_CA.crt") + + +# Common missing entries from the HTTP status code dict, basically anything +# GitHub reports that isn't basic HTTP/1.1. +responses[422] = 'Unprocessable Entity' + def charset_from_headers(headers): - """Parse charset from headers + """Parse charset from headers. :param httplib2.Response headers: Request headers :return: Defined encoding, or default to ASCII + """ match = re.search("charset=([^ ;]+)", headers.get('content-type', "")) if match: @@ -39,30 +107,42 @@ def charset_from_headers(headers): class GithubError(Exception): - """An error occured when making a request to the Github API.""" + + """An error occurred when making a request to the GitHub API.""" class HttpError(RuntimeError): - """A HTTP error occured when making a request to the Github API.""" + + """A HTTP error occurred when making a request to the GitHub API.""" + def __init__(self, message, content, code): - """Create a HttpError exception + """Create a HttpError exception. :param str message: Exception string :param str content: Full content of HTTP request :param int code: HTTP status code + """ self.args = (message, content, code) self.message = message self.content = content self.code = code - if code: + if code in responses: self.code_reason = responses[code] else: - self.code_reason = "" + self.code_reason = "" + LOGGER.warning('Unknown HTTP status %r, please file an issue', + code) class GithubRequest(object): - github_url = GITHUB_URL + + """Make an API request. + + :see: :class:`github2.client.Github` + + """ + url_format = "%(github_url)s/api/%(api_version)s/%(api_format)s" api_version = "v2" api_format = "json" @@ -70,15 +150,16 @@ class GithubRequest(object): def __init__(self, username=None, api_token=None, url_prefix=None, requests_per_second=None, access_token=None, - cache=None, proxy_host=None, proxy_port=None): - """Make an API request. - - :see: :class:`github2.client.Github` - """ + cache=None, proxy_host=None, proxy_port=None, + github_url=None): self.username = username self.api_token = api_token self.access_token = access_token self.url_prefix = url_prefix + if github_url is None: + self.github_url = DEFAULT_GITHUB_URL + else: + self.github_url = github_url if requests_per_second is None: self.delay = 0 else: @@ -90,25 +171,33 @@ def __init__(self, username=None, api_token=None, url_prefix=None, "api_version": self.api_version, "api_format": self.api_format, } - digicert_ha_cert = path.join(path.dirname(path.abspath(__file__ )), - "DigiCert_High_Assurance_EV_Root_CA.crt") if proxy_host is None: - self._http = httplib2.Http(cache=cache, ca_certs=digicert_ha_cert) + self._http = httplib2.Http(cache=cache) else: proxy_info = httplib2.ProxyInfo(httplib2.socks.PROXY_TYPE_HTTP, proxy_host, proxy_port) - self._http = httplib2.Http(proxy_info=proxy_info, cache=cache, - ca_certs=digicert_ha_cert) + self._http = httplib2.Http(proxy_info=proxy_info, cache=cache) + self._http.ca_certs = CA_CERTS + if SYSTEM_CERTS: + LOGGER.info('Using system certificates in %r', CA_CERTS) + elif CURL_CERTS: + LOGGER.info("Using cURL's certificates in %r", CA_CERTS) + else: + LOGGER.warning('Using bundled certificate for HTTPS connections') def encode_authentication_data(self, extra_post_data): + post_data = [] if self.access_token: - post_data = {"access_token": self.access_token} + post_data.append(("access_token", self.access_token)) elif self.username and self.api_token: - post_data = {"login": self.username, - "token": self.api_token} - else: - post_data = {} - post_data.update(extra_post_data) + post_data.append(("login", self.username)) + post_data.append(("token", self.api_token)) + for key, value in extra_post_data.items(): + if isinstance(value, list): + for elem in value: + post_data.append((key, elem)) + else: + post_data.append((key, value)) return urlencode(post_data) def get(self, *path_components): @@ -132,11 +221,10 @@ def delete(self, *path_components, **extra_post_data): def make_request(self, path, extra_post_data=None, method="GET"): if self.delay: - since_last = (datetime.datetime.now() - self.last_request) - since_last_in_seconds = (since_last.days * 24 * 60 * 60) + since_last.seconds + (since_last.microseconds/1000000.0) - if since_last_in_seconds < self.delay: - duration = self.delay - since_last_in_seconds - LOGGER.warning("delaying API call %s second(s)", duration) + since_last = (datetime.datetime.utcnow() - self.last_request) + if since_last.days == 0 and since_last.seconds < self.delay: + duration = self.delay - since_last.seconds + LOGGER.warning("delaying API call %g second(s)", duration) time.sleep(duration) extra_post_data = extra_post_data or {} @@ -144,7 +232,7 @@ def make_request(self, path, extra_post_data=None, method="GET"): result = self.raw_request(url, extra_post_data, method=method) if self.delay: - self.last_request = datetime.datetime.now() + self.last_request = datetime.datetime.utcnow() return result def raw_request(self, url, extra_post_data, method="GET"): diff --git a/github2/teams.py b/github2/teams.py index 859a40e..ca423c9 100644 --- a/github2/teams.py +++ b/github2/teams.py @@ -1,10 +1,22 @@ -from github2.core import BaseData, GithubCommand, Attribute +# Copyright (C) 2011-2012 James Rowe +# Patryk Zawadzki +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + +from github2.core import BaseData, GithubCommand, Attribute, requires_auth from github2.repositories import Repository from github2.users import User class Team(BaseData): - """.. versionadded:: 0.4.0""" + + """Team container. + + .. versionadded:: 0.4.0 + + """ + id = Attribute("The team id") name = Attribute("Name of the team") permission = Attribute("Permissions of the team") @@ -14,37 +26,60 @@ def __repr__(self): class Teams(GithubCommand): - """.. versionadded:: 0.4.0""" + + """GitHub API teams functionality. + + .. versionadded:: 0.4.0 + + """ + domain = "teams" def show(self, team_id): - """Get information on team_id + """Get information on team_id. :param int team_id: team to get information for + """ return self.get_value(str(team_id), filter="team", datatype=Team) def members(self, team_id): - """Get list of all team members + """Get list of all team members. :param int team_id: team to get information for + """ return self.get_values(str(team_id), "members", filter="users", datatype=User) + @requires_auth + def add_member(self, team_id, username): + """Add a new member to a team. + + :param int team_id: team to add new member to + :param str username: GitHub username to add to team + + """ + return self.get_values(str(team_id), 'members', method='POST', + post_data={'name': username}, filter='users', + datatype=User) + def repositories(self, team_id): - """Get list of all team members + """Get list of all team repositories. :param int team_id: team to get information for + """ return self.get_values(str(team_id), "repositories", filter="repositories", datatype=Repository) + @requires_auth def add_project(self, team_id, project): - """Add a project to a team + """Add a project to a team. :param int team_id: team to add repository to :param str project: GitHub project + """ if isinstance(project, Repository): project = project.project @@ -52,11 +87,13 @@ def add_project(self, team_id, project): post_data={'name': project}, filter="repositories", datatype=Repository) + @requires_auth def remove_project(self, team_id, project): - """Remove a project to a team + """Remove a project to a team. :param int team_id: team to remove project from :param str project: GitHub project + """ if isinstance(project, Repository): project = project.project diff --git a/github2/users.py b/github2/users.py index 3a40a8f..805fed4 100644 --- a/github2/users.py +++ b/github2/users.py @@ -1,9 +1,37 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2009-2012 Ask Solem +# James Rowe +# Sameer Al-Sakran +# Stéphane Angel +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + +try: + from urllib.parse import quote_plus # For Python 3 +except ImportError: + from urllib import quote_plus # NOQA + from github2.core import (BaseData, GithubCommand, DateAttribute, Attribute, enhanced_by_auth, requires_auth) -import urllib + + +class Key(BaseData): + + """SSH key container.""" + + id = Attribute('The key id') + key = Attribute('The SSH key data') + title = Attribute('The title for the SSH key') + + def __repr__(self): + return "" % self.id class User(BaseData): + + """GitHub user container.""" + id = Attribute("The user id") login = Attribute("The login username") name = Attribute("The users full name") @@ -28,9 +56,11 @@ class User(BaseData): format="user") def is_authenticated(self): - """Test for user auththenication + """Test for user authentication. + + :return bool: ``True`` if user is authenticated - :return bool: ``True`` if user is authenticated""" + """ return self.plan is not None def __repr__(self): @@ -38,63 +68,100 @@ def __repr__(self): class Users(GithubCommand): + + """GitHub API user functionality.""" + domain = "user" def search(self, query): - """Search for users + """Search for users. .. warning: Returns at most 100 users :param str query: term to search for + """ - return self.get_values("search", urllib.quote_plus(query), - filter="users", datatype=User) + return self.get_values("search", quote_plus(query), filter="users", + datatype=User) def search_by_email(self, query): - """Search for users by email address + """Search for users by email address. :param str query: email to search for + """ return self.get_value("email", query, filter="user", datatype=User) @enhanced_by_auth def show(self, username): - """Get information on Github user + """Get information on GitHub user. if ``username`` is ``None`` or an empty string information for the currently authenticated user is returned. - :param str username: Github user name + """ return self.get_value("show", username, filter="user", datatype=User) def followers(self, username): - """Get list of Github user's followers + """Get list of GitHub user's followers. + + :param str username: GitHub user name - :param str username: Github user name """ return self.get_values("show", username, "followers", filter="users") def following(self, username): - """Get list of users a Github user is following + """Get list of users a GitHub user is following. + + :param str username: GitHub user name - :param str username: Github user name """ return self.get_values("show", username, "following", filter="users") @requires_auth def follow(self, other_user): - """Follow a Github user + """Follow a GitHub user. + + :param str other_user: GitHub user name - :param str other_user: Github user name """ return self.get_values("follow", other_user, method="POST") @requires_auth def unfollow(self, other_user): - """Unfollow a Github user + """Unfollow a GitHub user. + + :param str other_user: GitHub user name - :param str other_user: Github user name """ return self.get_values("unfollow", other_user, method="POST") + + @requires_auth + def list_keys(self): + """Get list of SSH keys for the authenticated user.""" + return self.get_values('keys', filter='public_keys', datatype=Key) + + @requires_auth + def add_key(self, key, title=''): + """Add a SSH key for the authenticated user. + + :param str key: SSH key identifier + :param str title: Optional title for the SSH key + + """ + return self.get_values("key/add", + post_data={'key': key, 'title': title}, + method="POST", filter='public_keys', + datatype=Key) + + @requires_auth + def remove_key(self, key_id): + """Remove a SSH key for the authenticated user. + + :param int key_id: SSH key's GitHub identifier + + """ + return self.get_values('key/remove', post_data={'id': str(key_id)}, + filter='public_keys', datatype=Key) diff --git a/setup.py b/setup.py index 0abb26b..b72d775 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# Copyright (C) 2009-2012 Ask Solem +# Cody Soyland +# Donald von Stufft +# James Rowe +# Maximillian Dornseif +# Michael Basnight +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + import codecs import sys @@ -9,16 +19,22 @@ install_requires = ['httplib2 >= 0.7.0', ] -# simplejson is included in the standard library since Python 2.6 as json. -if sys.version_info[:2] < (2, 6): + +# simplejson is included in the standard library since Python 2.6 as json +if sys.version_info < (2, 5): + # 2.1 drops support for 2.4 + install_requires.append('simplejson >= 2.0.9, < 2.1') +elif sys.version_info[:2] < (2, 6): install_requires.append('simplejson >= 2.0.9') -extra = {} -if sys.version_info >= (3,): - install_requires.append('python-dateutil >= 2.0') - extra['use_2to3'] = True -else: +# dateutil supports python 2.x in dateutil-1, python 3.x in dateutil-2.0 and +# python 2.6+ in dateutil-2.1. Exciting… +if sys.version_info[:2] <= (2, 5): install_requires.append('python-dateutil < 2.0') +elif sys.version_info < (3, ): + install_requires.append('python-dateutil < 2.0, >= 2.1') +else: + install_requires.append('python-dateutil > 2.0') long_description = (codecs.open('README.rst', "r", "utf-8").read() + "\n" + codecs.open('NEWS.rst', "r", "utf-8").read()) @@ -64,5 +80,4 @@ "Topic :: Software Development", "Topic :: Software Development :: Libraries", ], - **extra ) diff --git a/tests/_setup.py b/tests/_setup.py deleted file mode 100644 index c410274..0000000 --- a/tests/_setup.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys - -# Forcibly insert path for `setup.py build` output, so that we import from the -# ``2to3`` converted sources. This is an ugly hack, but it saves an enormous -# amount of grief in handling Python 2 and 3. -sys.path.insert(0, 'build/lib') diff --git a/tests/data/github.com,api,v2,json,commits,list,JNRowe,jnrowe-misc,master,a252487bd992cdfc8247b3c41d0cb9fa b/tests/data/github.com,api,v2,json,commits,list,JNRowe,jnrowe-misc,master,a252487bd992cdfc8247b3c41d0cb9fa index 7128271..b88099b 100644 --- a/tests/data/github.com,api,v2,json,commits,list,JNRowe,jnrowe-misc,master,a252487bd992cdfc8247b3c41d0cb9fa +++ b/tests/data/github.com,api,v2,json,commits,list,JNRowe,jnrowe-misc,master,a252487bd992cdfc8247b3c41d0cb9fa @@ -2,7 +2,6 @@ status: 200 x-ratelimit-remaining: 59 content-location: https://github.com/api/v2/json/commits/list/JNRowe/jnrowe-misc/master?page=2 -content-encoding: gzip -set-cookie: _gh_sess=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--ed7eadd474fd37850b68bd3c08a5c55ad0c3c833; path=/; expires=Fri, 01 Jan 2021 00:00:00 GMT; secure; HttpOnly connection: keep-alive content-length: 22608 diff --git a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,d0ce7ae70e44f7f396a93485a24b3423 b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,d0ce7ae70e44f7f396a93485a24b3423 index 56b04cb..b15f434 100644 --- a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,d0ce7ae70e44f7f396a93485a24b3423 +++ b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,d0ce7ae70e44f7f396a93485a24b3423 @@ -2,7 +2,6 @@ status: 200 x-ratelimit-remaining: 59 content-location: https://github.com/api/v2/json/commits/list/JNRowe/misc-overlay/gh-pages -content-encoding: gzip -set-cookie: _gh_sess=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--ed7eadd474fd37850b68bd3c08a5c55ad0c3c833; path=/; expires=Fri, 01 Jan 2021 00:00:00 GMT; secure; HttpOnly connection: keep-alive content-length: 19508 diff --git a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,packages,dev-python.html,8622bc5e052f03439528cc9b7bf3fea2 b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,packages,dev-python.html,8622bc5e052f03439528cc9b7bf3fea2 index 3493ec2..a55f85d 100644 --- a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,packages,dev-python.html,8622bc5e052f03439528cc9b7bf3fea2 +++ b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,gh-pages,packages,dev-python.html,8622bc5e052f03439528cc9b7bf3fea2 @@ -2,7 +2,6 @@ status: 200 x-ratelimit-remaining: 58 content-location: https://github.com/api/v2/json/commits/list/JNRowe/misc-overlay/gh-pages/packages/dev-python.html -content-encoding: gzip -set-cookie: _gh_sess=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--ed7eadd474fd37850b68bd3c08a5c55ad0c3c833; path=/; expires=Fri, 01 Jan 2021 00:00:00 GMT; secure; HttpOnly connection: keep-alive content-length: 19508 diff --git a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,Makefile,75d9a1e41e76c3b924420d48b56dc756 b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,Makefile,75d9a1e41e76c3b924420d48b56dc756 index eb2d090..d420f44 100644 --- a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,Makefile,75d9a1e41e76c3b924420d48b56dc756 +++ b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,Makefile,75d9a1e41e76c3b924420d48b56dc756 @@ -2,7 +2,6 @@ status: 200 x-ratelimit-remaining: 59 content-location: https://github.com/api/v2/json/commits/list/JNRowe/misc-overlay/master/Makefile -content-encoding: gzip -set-cookie: _gh_sess=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--ed7eadd474fd37850b68bd3c08a5c55ad0c3c833; path=/; expires=Fri, 01 Jan 2021 00:00:00 GMT; secure; HttpOnly connection: keep-alive content-length: 19364 diff --git a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,bfb4f990e48c87dab73d988a81318d69 b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,bfb4f990e48c87dab73d988a81318d69 index 33b6332..e6d80d7 100644 --- a/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,bfb4f990e48c87dab73d988a81318d69 +++ b/tests/data/github.com,api,v2,json,commits,list,JNRowe,misc-overlay,master,bfb4f990e48c87dab73d988a81318d69 @@ -2,7 +2,6 @@ status: 200 x-ratelimit-remaining: 59 content-location: https://github.com/api/v2/json/commits/list/JNRowe/misc-overlay/master -content-encoding: gzip -set-cookie: _gh_sess=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--ed7eadd474fd37850b68bd3c08a5c55ad0c3c833; path=/; expires=Fri, 01 Jan 2021 00:00:00 GMT; secure; HttpOnly connection: keep-alive content-length: 23425 diff --git a/tests/data/github.com,api,v2,json,commits,show,ask,python-github2,1c83cde9b5a7c396a01af1007fb7b88765b9ae45,a4f58221c8131c4e0975cd806f13d76c b/tests/data/github.com,api,v2,json,commits,show,ask,python-github2,1c83cde9b5a7c396a01af1007fb7b88765b9ae45,a4f58221c8131c4e0975cd806f13d76c index 6340de4..e7cf6dc 100644 --- a/tests/data/github.com,api,v2,json,commits,show,ask,python-github2,1c83cde9b5a7c396a01af1007fb7b88765b9ae45,a4f58221c8131c4e0975cd806f13d76c +++ b/tests/data/github.com,api,v2,json,commits,show,ask,python-github2,1c83cde9b5a7c396a01af1007fb7b88765b9ae45,a4f58221c8131c4e0975cd806f13d76c @@ -2,7 +2,6 @@ status: 200 x-ratelimit-remaining: 59 content-location: https://github.com/api/v2/json/commits/show/ask/python-github2/1c83cde9b5a7c396a01af1007fb7b88765b9ae45 -content-encoding: gzip -set-cookie: _gh_sess=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--ed7eadd474fd37850b68bd3c08a5c55ad0c3c833; path=/; expires=Fri, 01 Jan 2021 00:00:00 GMT; secure; HttpOnly connection: keep-alive content-length: 1578 diff --git a/tests/data/github.com,api,v2,json,issues,list,JNRowe,misc-overlay,label,bug,79a89664a2d4783ee9089d29f99b660e b/tests/data/github.com,api,v2,json,issues,list,JNRowe,misc-overlay,label,bug,79a89664a2d4783ee9089d29f99b660e new file mode 100644 index 0000000..e865e18 --- /dev/null +++ b/tests/data/github.com,api,v2,json,issues,list,JNRowe,misc-overlay,label,bug,79a89664a2d4783ee9089d29f99b660e @@ -0,0 +1,16 @@ +status: 200 +x-ratelimit-remaining: 59 +content-location: https://github.com/api/v2/json/issues/list/JNRowe/misc-overlay/label/bug +connection: keep-alive +x-next: https://github.com/api/v2/json/issues/list/JNRowe/misc-overlay/label/bug?page=2 +content-length: 15566 +server: nginx/0.7.67 +date: Wed, 08 Jun 2011 13:42:00 GMT +x-runtime: 138ms +x-ratelimit-limit: 60 +etag: "f625ba2e14adae8f3b8960c00d60e9eb" +cache-control: private, max-age=0, must-revalidate +x-last: https://github.com/api/v2/json/issues/list/JNRowe/misc-overlay/label/bug?page=2 +content-type: application/json; charset=utf-8 + +{"issues":[{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":1.0,"number":3,"votes":0,"created_at":"2010/09/14 17:51:13 -0700","comments":1,"body":"\n---\n`ditz-id`: `05d00a48cdce80119a2b26f7c6f0e25894ff8537`\n\n`ditz-event-time`: `2010-03-04T19:21:01.799951Z`","title":"app-text/gist file locations have changed in git repo.","updated_at":"2010/09/14 17:51:17 -0700","closed_at":"2010/09/14 17:51:17 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/3","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":4.0,"number":9,"votes":0,"created_at":"2010/09/14 17:52:17 -0700","comments":1,"body":"\n---\n`ditz-id`: `0fdb57d80db65f2370811df882adbc8e958e5d8a`\n\n`ditz-event-time`: `2009-12-21T17:53:02.947952Z`","title":"fossil uses internal copy of sqlite.","updated_at":"2010/09/14 17:52:21 -0700","closed_at":"2010/09/14 17:52:21 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/9","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":6.0,"number":11,"votes":0,"created_at":"2010/09/14 17:52:40 -0700","comments":1,"body":"\n---\n`ditz-id`: `137ec0032fe83918fd0570e125dc1f2991a37676`\n\n`ditz-event-time`: `2010-03-04T19:37:23.671948Z`","title":"dev-python/pycukes fails with recent distutills.eclass changes.","updated_at":"2010/09/14 17:52:44 -0700","closed_at":"2010/09/14 17:52:44 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/11","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":19.0,"number":24,"votes":0,"created_at":"2010/09/14 17:54:52 -0700","comments":1,"body":"\n---\n`ditz-id`: `2ca7641993a1f7a1a8cb7ab01d5cde6b874e118a`\n\n`ditz-event-time`: `2009-12-14T10:32:37.855964Z`","title":"app-text/apvlv-0.0.8.1 has automagic djvu dependency.","updated_at":"2010/09/14 17:54:57 -0700","closed_at":"2010/09/14 17:54:57 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/24","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":24.0,"number":29,"votes":0,"created_at":"2010/09/14 17:55:30 -0700","comments":2,"body":"Reported by Dennis Bruce.\n\n---\n`ditz-id`: `32197e2725b7a973e0a66f3ecaf0efbbfb966c1e`\n\n`ditz-event-time`: `2010-03-04T17:43:17.471958Z`","title":"dev-util/ccontrol fails in src_test.","updated_at":"2010/09/14 17:55:35 -0700","closed_at":"2010/09/14 17:55:35 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/29","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":25.0,"number":30,"votes":0,"created_at":"2010/09/14 17:55:37 -0700","comments":1,"body":"\n---\n`ditz-id`: `337caa2e1f972335878906cc1fbfd6091a96d633`\n\n`ditz-event-time`: `2010-03-05T21:27:08.459986Z`","title":"python.eclass changes emit warnings with jnrowe-pypi:module_script_wrapper().","updated_at":"2010/09/14 17:55:46 -0700","closed_at":"2010/09/14 17:55:46 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/30","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":28.0,"number":33,"votes":0,"created_at":"2010/09/14 17:55:59 -0700","comments":1,"body":"\n---\n`ditz-id`: `35cca70ded236159c5a3a18bf816c2af345bc811`\n\n`ditz-event-time`: `2010-07-05T17:16:15.503883Z`","title":"dev-libs/ctpl cupage check blocked by robots.txt.","updated_at":"2010/09/14 17:56:03 -0700","closed_at":"2010/09/14 17:56:03 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/33","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":29.0,"number":34,"votes":0,"created_at":"2010/09/14 17:56:06 -0700","comments":1,"body":"\n---\n`ditz-id`: `362778bbc4dd3a91ab4149f33868039c75ccc7d6`\n\n`ditz-event-time`: `2010-02-14T07:36:28.091756Z`","title":"dev-python/html doesn't work with Python 2.4.","updated_at":"2010/09/14 17:56:10 -0700","closed_at":"2010/09/14 17:56:10 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/34","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":33.0,"number":38,"votes":0,"created_at":"2010/09/14 17:56:28 -0700","comments":1,"body":"\n---\n`ditz-id`: `3a12af55a7e27b7a87cf0432c7285e4e32ace4fe`\n\n`ditz-event-time`: `2009-12-06T06:21:55.723974Z`","title":"Invalid man page install path in apvlv.","updated_at":"2010/09/14 17:56:38 -0700","closed_at":"2010/09/14 17:56:38 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/38","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":34.0,"number":39,"votes":0,"created_at":"2010/09/14 17:56:44 -0700","comments":1,"body":"\n---\n`ditz-id`: `3a3471c3a9218713445122c21458c5d7f1b72e30`\n\n`ditz-event-time`: `2010-03-04T17:44:37.431956Z`","title":"dev-util/gitserve fails with recent distutills.eclass changes.","updated_at":"2010/09/14 17:56:49 -0700","closed_at":"2010/09/14 17:56:49 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/39","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":35.0,"number":40,"votes":0,"created_at":"2010/09/14 17:56:51 -0700","comments":1,"body":"The download page has changed layout.\n\n---\n`ditz-id`: `408d842ddd5caef873f5bedcaf4e71cee992d058`\n\n`ditz-event-time`: `2009-12-31T03:25:36.464053Z`","title":"matwm2 cupage entry broken.","updated_at":"2010/09/14 17:56:56 -0700","closed_at":"2010/09/14 17:56:56 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/40","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":40.0,"number":45,"votes":0,"created_at":"2010/09/14 17:57:35 -0700","comments":1,"body":"- /usr/share/doc/apvlv-0.0.7.4/reg.png - /usr/share/doc/apvlv-0.0.7.4/dir.png - /usr/share/doc/apvlv-0.0.7.4/pdf.png\n\n---\n`ditz-id`: `48578b8bac650dad93271d2880de33140768b4f0`\n\n`ditz-event-time`: `2009-11-14T06:53:02.847996Z`","title":"apvlv stores datafiles in docdir.","updated_at":"2010/09/14 17:57:39 -0700","closed_at":"2010/09/14 17:57:39 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/45","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":49.0,"number":54,"votes":0,"created_at":"2010/09/14 17:59:00 -0700","comments":1,"body":"Some of the plugins reference the build system's default document path, which is overridden at install time. This isn't just cosmetic, it breaks help functionality in the interface.\n\n---\n`ditz-id`: `54418badf2a50f642b919f458dd561e559083e7b`\n\n`ditz-event-time`: `2010-06-20T12:35:08.378462Z`","title":"Invalid doc path used in geany-plugins-0.19.","updated_at":"2010/09/14 17:59:10 -0700","closed_at":"2010/09/14 17:59:10 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/54","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":81.0,"number":86,"votes":0,"created_at":"2010/09/14 18:08:09 -0700","comments":1,"body":"ebuild and cupage entry need updating.\n\n---\n`ditz-id`: `81ea1ed5020396c7114643281ad2bf554133f9d6`\n\n`ditz-event-time`: `2009-11-16T05:25:05.255933Z`","title":"x11-wm/parti has moved to Google's code hosting.","updated_at":"2010/09/14 18:08:13 -0700","closed_at":"2010/09/14 18:08:13 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/86","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":97.0,"number":102,"votes":0,"created_at":"2010/09/14 18:11:24 -0700","comments":6,"body":"\n---\n`ditz-id`: `971e2fc2ad70b118b79adfe1a3745e5c551932bf`\n\n`ditz-event-time`: `2009-11-12T04:40:21.007927Z`","title":"Packages missing cupage config entries.","updated_at":"2010/09/14 18:11:37 -0700","closed_at":"2010/09/14 18:11:37 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/102","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":113.0,"number":118,"votes":0,"created_at":"2010/09/14 18:13:58 -0700","comments":1,"body":"\n---\n`ditz-id`: `a7598bb237c7bb2c402c4ff4c6a4e59d543e998f`\n\n`ditz-event-time`: `2010-03-04T19:17:31.451916Z`","title":"dev-python/restview fails with recent distutills.eclass changes.","updated_at":"2010/09/14 18:14:02 -0700","closed_at":"2010/09/14 18:14:02 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/118","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":117.0,"number":122,"votes":0,"created_at":"2010/09/14 18:14:23 -0700","comments":1,"body":"\n---\n`ditz-id`: `afa05d6a5f6ccdebcdaebec759d408d61888e729`\n\n`ditz-event-time`: `2010-07-05T17:21:11.219887Z`","title":"www-client/opera-remote cupage check blocked by robots.txt.","updated_at":"2010/09/14 18:14:28 -0700","closed_at":"2010/09/14 18:14:28 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/122","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":122.0,"number":127,"votes":0,"created_at":"2010/09/14 18:15:29 -0700","comments":1,"body":"\n---\n`ditz-id`: `b48295c6d581976a0c95f26c2190865c493fc081`\n\n`ditz-event-time`: `2010-07-05T17:17:40.083880Z`","title":"dev-libs/luaposix cupage check blocked by robots.txt.","updated_at":"2010/09/14 18:15:39 -0700","closed_at":"2010/09/14 18:15:39 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/127","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":0.0,"number":141,"votes":0,"created_at":"2010/09/14 18:17:45 -0700","comments":3,"body":"\n---\n`ditz-id`: `c8d8078898af58f80139f986306433a4c4efd397`\n\n`ditz-event-time`: `2010-07-05T17:23:22.063887Z`","title":"media-gfx/psplash cupage check raises 403.","updated_at":"2011/02/25 22:13:20 -0800","closed_at":"2011/02/25 22:13:20 -0800","html_url":"https://github.com/JNRowe/misc-overlay/issues/141","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":150.0,"number":155,"votes":0,"created_at":"2010/09/14 18:20:01 -0700","comments":1,"body":"Uses incorrect path.\n\n---\n`ditz-id`: `d3ba89ca556458709fe431eb3ad43989722cf274`\n\n`ditz-event-time`: `2010-06-22T18:17:54.823885Z`","title":"net-misc/bleeter-0.5.0 manpage generation broken.","updated_at":"2010/09/14 18:20:12 -0700","closed_at":"2010/09/14 18:20:12 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/155","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":166.0,"number":171,"votes":0,"created_at":"2010/09/14 18:22:48 -0700","comments":1,"body":"\n---\n`ditz-id`: `f86c46636c26fd30d5d812fc207a8ea9107c7bdc`\n\n`ditz-event-time`: `2009-12-13T11:35:43.211823Z`","title":"taskwarrior overrides user CFLAGS settings.","updated_at":"2010/09/14 18:22:53 -0700","closed_at":"2010/09/14 18:22:53 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/171","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":11.0,"number":199,"votes":0,"created_at":"2010/10/07 18:17:27 -0700","comments":2,"body":"This pypi entry seems to have been deleted. Leave this bug open for a few weeks to see if it is re-added, if not look for alternatives.","title":"dev-python/termstyle cupage check raises 404.","updated_at":"2010/11/05 05:53:12 -0700","closed_at":"2010/11/05 05:53:12 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/199","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":177.0,"number":200,"votes":0,"created_at":"2010/10/08 04:06:56 -0700","comments":1,"body":"Thanks to Daniel Brew for reporting.","title":"media-gfx/sng won't build with libpng-1.4.","updated_at":"2010/10/08 04:33:06 -0700","closed_at":"2010/10/08 04:33:06 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/200","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":18.0,"number":258,"votes":0,"created_at":"2011/01/09 22:53:39 -0800","comments":2,"body":"Recent sourceforge changes have broken matcher.","title":"sci-geosciences/gpsfeed cupage check fails.","updated_at":"2011/01/10 23:47:24 -0800","closed_at":"2011/01/10 23:47:24 -0800","html_url":"https://github.com/JNRowe/misc-overlay/issues/258","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":19.0,"number":259,"votes":0,"created_at":"2011/01/09 23:02:37 -0800","comments":2,"body":"Recent sourceforge changes have broken matcher.","title":"media-gfx/sng cupage check fails.","updated_at":"2011/01/10 22:04:52 -0800","closed_at":"2011/01/10 22:04:52 -0800","html_url":"https://github.com/JNRowe/misc-overlay/issues/259","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":20.0,"number":261,"votes":0,"created_at":"2011/01/09 23:04:31 -0800","comments":2,"body":"Recent sourceforge changes have broken matcher.","title":"dev-tcltk/tcludp cupage check fails.","updated_at":"2011/01/10 22:04:52 -0800","closed_at":"2011/01/10 22:04:52 -0800","html_url":"https://github.com/JNRowe/misc-overlay/issues/261","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":6.0,"number":262,"votes":0,"created_at":"2011/01/09 23:44:53 -0800","comments":2,"body":"If `USE=!examples` the links in the documentation will be broken.\r\n\r\nThis obviously also applies to `mail-filter/maildirproc-python2`.","title":"mail-filter/maildirproc documentation should link to upstream for examples","updated_at":"2011/01/10 23:47:24 -0800","closed_at":"2011/01/10 23:47:24 -0800","html_url":"https://github.com/JNRowe/misc-overlay/issues/262","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":0.0,"number":284,"votes":0,"created_at":"2011/02/14 03:00:42 -0800","comments":2,"body":"Upstream appears to have redesigned their site, so the cupage check needs updating.","title":"dev-python/texttable cupage check raises 404.","updated_at":"2011/02/15 05:54:47 -0800","closed_at":"2011/02/15 05:54:47 -0800","html_url":"https://github.com/JNRowe/misc-overlay/issues/284","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":9.0,"number":327,"votes":0,"created_at":"2011/04/04 21:42:38 -0700","comments":1,"body":"Appears to have switched to PyPI for listings.","title":"dev-python/feedcache cupage entry broken.","updated_at":"2011/04/05 03:59:24 -0700","closed_at":"2011/04/05 03:59:24 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/327","user":"JNRowe","labels":["bug"],"state":"closed"},{"gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","position":10.0,"number":328,"votes":0,"created_at":"2011/04/04 21:44:12 -0700","comments":1,"body":"Files have been repackaged and renamed.","title":"app-misc/libeatmydata cupage entry broken.","updated_at":"2011/04/05 03:59:24 -0700","closed_at":"2011/04/05 03:59:24 -0700","html_url":"https://github.com/JNRowe/misc-overlay/issues/328","user":"JNRowe","labels":["bug"],"state":"closed"}]} \ No newline at end of file diff --git a/tests/data/github.com,api,v2,json,organizations,JNRowe-test-org,teams,6869b96ae6fbaa7511d92aa73803898b b/tests/data/github.com,api,v2,json,organizations,JNRowe-test-org,teams,6869b96ae6fbaa7511d92aa73803898b new file mode 100644 index 0000000..969ee88 --- /dev/null +++ b/tests/data/github.com,api,v2,json,organizations,JNRowe-test-org,teams,6869b96ae6fbaa7511d92aa73803898b @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 60 +content-location: https://github.com/api/v2/json/organizations/JNRowe-test-org/teams +x-runtime: 20ms +content-length: 61 +server: nginx/1.0.4 +connection: keep-alive +x-ratelimit-limit: 58 +etag: "0ea8a446a00b4d7894676a34bd8d501f" +cache-control: private, max-age=0, must-revalidate +date: Mon, 19 Dec 2011 20:50:41 GMT +content-type: application/json; charset=utf-8 + +{"team":{"name":"team_pull","id":121678,"permission":"pull"}} diff --git a/tests/data/github.com,api,v2,json,repos,show,JNRowe,misc-overlay,languages,941f2fae40c32a86b82f8e51a623cacd b/tests/data/github.com,api,v2,json,repos,show,JNRowe,misc-overlay,languages,941f2fae40c32a86b82f8e51a623cacd new file mode 100644 index 0000000..15bd799 --- /dev/null +++ b/tests/data/github.com,api,v2,json,repos,show,JNRowe,misc-overlay,languages,941f2fae40c32a86b82f8e51a623cacd @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 59 +content-location: https://github.com/api/v2/json/repos/show/JNRowe/misc-overlay/languages +x-runtime: 93ms +content-length: 40 +server: nginx/0.7.67 +connection: keep-alive +x-ratelimit-limit: 60 +etag: "584c4c914a780c9338d00d146803076b" +cache-control: private, max-age=0, must-revalidate +date: Wed, 08 Jun 2011 13:38:58 GMT +content-type: application/json; charset=utf-8 + +{"languages":{"VimL":82,"Python":11194}} \ No newline at end of file diff --git a/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,branches,2be8c8f036124b61fbddeecbbdf28e9c b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,branches,2be8c8f036124b61fbddeecbbdf28e9c new file mode 100644 index 0000000..ab694a6 --- /dev/null +++ b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,branches,2be8c8f036124b61fbddeecbbdf28e9c @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 57 +content-location: https://github.com/api/v2/json/repos/show/ask/python-github2/branches +x-runtime: 8ms +content-length: 66 +server: nginx/0.7.67 +connection: keep-alive +x-ratelimit-limit: 60 +etag: "9f005bc97eb5ab622f01f1810790a947" +cache-control: private, max-age=0, must-revalidate +date: Wed, 08 Jun 2011 13:39:24 GMT +content-type: application/json; charset=utf-8 + +{"branches":{"master":"1c83cde9b5a7c396a01af1007fb7b88765b9ae45"}} \ No newline at end of file diff --git a/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,collaborators,855cea60d613a363106762311e6f53ea b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,collaborators,855cea60d613a363106762311e6f53ea new file mode 100644 index 0000000..26c614b --- /dev/null +++ b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,collaborators,855cea60d613a363106762311e6f53ea @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 57 +content-location: https://github.com/api/v2/json/repos/show/ask/python-github2/collaborators?access_token=xxx +x-runtime: 15ms +content-length: 50 +server: nginx/0.7.67 +connection: keep-alive +x-ratelimit-limit: 60 +etag: "03a973e8cc6d6cc0a8f05f2ef767a175" +cache-control: private, max-age=0, must-revalidate +date: Wed, 08 Jun 2011 13:38:17 GMT +content-type: application/json; charset=utf-8 + +{"collaborators":["ask","jdunck","JNRowe","nvie"]} \ No newline at end of file diff --git a/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,tags,e7b38a20b45dffd2a22d34b5b51f41da b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,tags,e7b38a20b45dffd2a22d34b5b51f41da new file mode 100644 index 0000000..617036e --- /dev/null +++ b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,tags,e7b38a20b45dffd2a22d34b5b51f41da @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 58 +content-location: https://github.com/api/v2/json/repos/show/ask/python-github2/tags +x-runtime: 7ms +content-length: 372 +server: nginx/0.7.67 +connection: keep-alive +x-ratelimit-limit: 60 +etag: "25b6801155cc07a688ae97ce708404aa" +cache-control: private, max-age=0, must-revalidate +date: Wed, 08 Jun 2011 13:39:16 GMT +content-type: application/json; charset=utf-8 + +{"tags":{"0.4.0":"6337009b8cefecaff3d5b73883d3da541cc25432","v0.2.0":"9c573faef38b14603c8bdca39c0b7a0140502da9","v0.3.0":"573da9892ddbe85272b7367bf2936d1a3d5d3868","v0.1.2":"03afc58f59b0023a0a9c880387b0487b9010ca98","0.4.1":"96b0a41dd249c521323700bc11a0a721a7c9e642","v0.3.1":"f4c0a1f94770dd087fe636ed98f3443db317ab8c","v0.1.3":"210b2ac7f9368e372d17b7c932e3ad08c23530d4"}} \ No newline at end of file diff --git a/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,watchers,8f4f641783ac93b3f5d7507bc65cf925 b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,watchers,8f4f641783ac93b3f5d7507bc65cf925 new file mode 100644 index 0000000..cd08980 --- /dev/null +++ b/tests/data/github.com,api,v2,json,repos,show,ask,python-github2,watchers,8f4f641783ac93b3f5d7507bc65cf925 @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 56 +content-location: https://github.com/api/v2/json/repos/show/ask/python-github2/watchers +x-runtime: 54ms +content-length: 1625 +server: nginx/0.7.67 +connection: keep-alive +x-ratelimit-limit: 60 +etag: "b358d3db0f531bd5a4942821a7b2c207" +cache-control: private, max-age=0, must-revalidate +date: Wed, 08 Jun 2011 13:39:31 GMT +content-type: application/json; charset=utf-8 + +{"watchers":["ask","bartTC","gregnewman","sneeu","playpauseandstop","bradjasper","tav","joestump","montylounge","justquick","annoma","mrevilme","matagus","stefanfoulis","paulproteus","mattdennewitz","mrolafsson","mlaprise","notesandvolts","idangazit","defunkt","ericholscher","travisjeffery","mnaberez","mikexstudios","robertpfeiffer","diox","mmalone","rconradharris","codysoyland","fperez","koenbollen","mager","eokyere","leegao","nvie","storborg","thoas","Taomio","junckritter","eklitzke","claudiob","yyliuliang","hemanth","dinoboff","hoffmann","Kuze","sixthgear","wasabi1809","CMB","dcolish","tomdyson","miyamuko","stevejalim","grauwoelfchen","pydanny","mgrouchy","kennethreitz","mrtazz","durden","tkaemming","rnelson","jbochi","cloudartisan","jdunck","mattoufoutu","MtvnGames","salsakran","jrabbit","ojii","shabda","JNRowe","zsiciarz","rnagle","imp","rdegges","travlr","johl","gregory80","markuso","meantheory","idorosen","svetlyak40wt","adamv","dforsyth","bleepbloop","ChristopherMacGown","dreynolds","TaurusOlson","rstrobl","johndagostino","droot","pierre-roux","sanjayprabhu","pquerna","iki","mt3","RaiMan","pinoystartup","adamdoupe","RafeKettler","glensc","wermut","switchyard","sammyt","patrys","blackspiraldev","fkling","rokstrnisa","myusuf3","xen-git","bassdread","pmuilu","pombredanne","sigurdga","surajram","loganlinn","k7d","throughnothing","Amper","nikescar","deeGraYve","broderboy","aculich","AntonioMeireles","pikhovkin","atkinson","sc68cal","ralphbean","goosemo","ergelo","gazoombo","zikey","hub-cap","jamesadney","EnTeQuAk","albertz","ryansb","misfire","lxneng","jean-philippe","deniszgonjanin","Irazmus"]} \ No newline at end of file diff --git a/tests/data/github.com,api,v2,json,teams,121678,repositories,access_token=xxx,00192d3a6cc4fc297b5a9cb0742c3663 b/tests/data/github.com,api,v2,json,teams,121678,repositories,access_token=xxx,00192d3a6cc4fc297b5a9cb0742c3663 new file mode 100644 index 0000000..c83a9c7 --- /dev/null +++ b/tests/data/github.com,api,v2,json,teams,121678,repositories,access_token=xxx,00192d3a6cc4fc297b5a9cb0742c3663 @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 60 +content-location: https://github.com/api/v2/json/organizations/JNRowe-test-org/teams +x-runtime: 20ms +content-length: 676 +server: nginx/1.0.4 +connection: keep-alive +x-ratelimit-limit: 58 +etag: "397a2fcea970a6391b74eabcb62ed265" +cache-control: private, max-age=0, must-revalidate +date: Thu, 19 Dec 2011 20:50:41 GMT +content-type: application/json; charset=utf-8 + +{"repositories":[{"open_issues":0,"description":"","watchers":1,"forks":1,"has_issues":true,"has_downloads":true,"fork":false,"created_at":"2011/12/18 17:36:08 -0800","homepage":"","size":0,"private":false,"name":"test1","owner":"JNRowe-test-org","has_wiki":true,"url":"https://github.com/JNRowe-test-org/test1","organization":"JNRowe-test-org"},{"open_issues":0,"description":"","watchers":1,"forks":1,"has_issues":true,"has_downloads":true,"fork":false,"created_at":"2011/12/18 17:36:24 -0800","homepage":"","size":0,"private":false,"name":"test2","owner":"JNRowe-test-org","has_wiki":true,"url":"https://github.com/JNRowe-test-org/test2","organization":"JNRowe-test-org"}]} diff --git a/tests/data/github.com,api,v2,json,teams,121990,members,ef66ad4c5f8c1b21dc9bc8c54c9ec91e b/tests/data/github.com,api,v2,json,teams,121990,members,ef66ad4c5f8c1b21dc9bc8c54c9ec91e new file mode 100644 index 0000000..10bd256 --- /dev/null +++ b/tests/data/github.com,api,v2,json,teams,121990,members,ef66ad4c5f8c1b21dc9bc8c54c9ec91e @@ -0,0 +1,14 @@ +status: 200 +x-ratelimit-remaining: 60 +content-location: https://github.com/api/v2/json/teams/121990/members +x-runtime: 20ms +content-length: 202 +server: nginx/1.0.4 +connection: keep-alive +x-ratelimit-limit: 58 +etag: "397a2fcea970a6391b74eabcb62ed265" +cache-control: private, max-age=0, must-revalidate +date: Thu, 19 Dec 2011 20:55:34 GMT +content-type: application/json; charset=utf-8 + +{"users":[{"name":"James Rowe","gravatar_id":"e40de1eb6e8a74cb96b3f07f3994f155","location":"Cambridge, UK","blog":"http://jnrowe.github.com/","type":"User","login":"JNRowe","email":"jnrowe@gmail.com"}]} diff --git a/tests/data/github.com,api,v2,json,user,keys,access_token=xxx,f95947b79ef9d668b2ebac9a591a2a36 b/tests/data/github.com,api,v2,json,user,keys,access_token=xxx,f95947b79ef9d668b2ebac9a591a2a36 new file mode 100644 index 0000000..ef84a1d --- /dev/null +++ b/tests/data/github.com,api,v2,json,user,keys,access_token=xxx,f95947b79ef9d668b2ebac9a591a2a36 @@ -0,0 +1,16 @@ +status: 200 +x-ratelimit-remaining: 60 +content-location: https://github.com/api/v2/json/user/keys?access_token=xxx +-content-encoding: gzip +connection: keep-alive +content-length: 94 +server: nginx/1.0.4 +x-runtime: 7 +x-ratelimit-limit: 60 +etag: "1df3e8616495dcf01a4fb79fa450c544" +cache-control: private, max-age=0, must-revalidate +date: Fri, 10 Feb 2012 13:53:29 GMT +x-frame-options: deny +content-type: application/json; charset=utf-8 + +{"public_keys":[{"title":"My sekret key","id":1337,"key":"ssh-rsa DoYouSee key@example.com"}]} diff --git a/tests/test_charset_header.py b/tests/test_charset_header.py index 073e10c..2786673 100644 --- a/tests/test_charset_header.py +++ b/tests/test_charset_header.py @@ -1,14 +1,18 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. -from nose.tools import assert_equals +from nose.tools import eq_ from github2.request import charset_from_headers def no_match_test(): d = {} - assert_equals("ascii", charset_from_headers(d)) + eq_("ascii", charset_from_headers(d)) + def utf_test(): d = {'content-type': 'application/json; charset=utf-8'} - assert_equals("utf-8", charset_from_headers(d)) + eq_("utf-8", charset_from_headers(d)) diff --git a/tests/test_commits.py b/tests/test_commits.py index 06f3b1c..b27fbd3 100644 --- a/tests/test_commits.py +++ b/tests/test_commits.py @@ -1,48 +1,71 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. -from nose.tools import assert_equals +from datetime import datetime + +from nose.tools import eq_ import utils -class Commit(utils.HttpMockTestCase): +class CommitProperties(utils.HttpMockTestCase): + + """Test commit property handling.""" + + commit_id = '1c83cde9b5a7c396a01af1007fb7b88765b9ae45' + + def test_commit(self): + commit = self.client.commits.show('ask/python-github2', self.commit_id) + eq_(commit.message, 'Added cache support to manage_collaborators.') + eq_(commit.parents, + [{"id": '7d1c855d2f44a55e4b90b40017be697cf70cb4a0'}]) + eq_(commit.url, '/ask/python-github2/commit/%s' % self.commit_id) + eq_(commit.author['login'], 'JNRowe') + eq_(commit.id, self.commit_id) + eq_(commit.committed_date, datetime(2011, 6, 6, 16, 13, 50)) + eq_(commit.authored_date, datetime(2011, 6, 6, 16, 13, 50)) + eq_(commit.tree, 'f48fcc1a0b8ea97f3147dc42cf7cdb6683493e94') + eq_(commit.committer['login'], 'JNRowe') + eq_(commit.added, None) + eq_(commit.removed, None) + eq_(commit.modified[0]['filename'], + 'github2/bin/manage_collaborators.py') + def test_repr(self): - commit_id = '1c83cde9b5a7c396a01af1007fb7b88765b9ae45' - commit = self.client.commits.show('ask/python-github2', commit_id) - assert_equals(repr(commit), - '' % commit_id[:8]) + commit = self.client.commits.show('ask/python-github2', self.commit_id) + eq_(repr(commit), + '' % self.commit_id[:8]) class CommitsQueries(utils.HttpMockTestCase): + """Test commit querying""" + def test_list(self): commits = self.client.commits.list('JNRowe/misc-overlay') - assert_equals(len(commits), 35) - assert_equals(commits[0].id, - '4de0834d58b37ef3020c49df43c95649217a2def') + eq_(len(commits), 35) + eq_(commits[0].id, '4de0834d58b37ef3020c49df43c95649217a2def') def test_list_with_page(self): commits = self.client.commits.list('JNRowe/jnrowe-misc', page=2) - assert_equals(len(commits), 35) - assert_equals(commits[0].id, - '1f5ad2c3206bafc4aca9e6ce50f5c605befdb3d6') + eq_(len(commits), 35) + eq_(commits[0].id, '1f5ad2c3206bafc4aca9e6ce50f5c605befdb3d6') def test_list_with_branch(self): commits = self.client.commits.list('JNRowe/misc-overlay', 'gh-pages') - assert_equals(len(commits), 35) - assert_equals(commits[0].id, - '025148bdaa6fb6bdac9c3522d481fadf1c0a456f') + eq_(len(commits), 35) + eq_(commits[0].id, '025148bdaa6fb6bdac9c3522d481fadf1c0a456f') def test_list_with_file(self): commits = self.client.commits.list('JNRowe/misc-overlay', file='Makefile') - assert_equals(len(commits), 35) - assert_equals(commits[0].id, - 'fc12b924d34dc38c8ce76d27a866221faa88cb72') + eq_(len(commits), 35) + eq_(commits[0].id, 'fc12b924d34dc38c8ce76d27a866221faa88cb72') def test_list_with_branch_and_file(self): commits = self.client.commits.list('JNRowe/misc-overlay', 'gh-pages', 'packages/dev-python.html') - assert_equals(len(commits), 35) - assert_equals(commits[0].id, - '025148bdaa6fb6bdac9c3522d481fadf1c0a456f') + eq_(len(commits), 35) + eq_(commits[0].id, '025148bdaa6fb6bdac9c3522d481fadf1c0a456f') diff --git a/tests/test_date_handling.py b/tests/test_date_handling.py index 591bb2c..b696a74 100644 --- a/tests/test_date_handling.py +++ b/tests/test_date_handling.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- - -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. from datetime import datetime as dt -from nose.tools import assert_equals +from nose.tools import eq_ from github2.core import (datetime_to_ghdate, datetime_to_commitdate, datetime_to_isodate, string_to_datetime) @@ -14,153 +16,138 @@ # naïve datetime objects used in the current code base def test_ghdate_to_datetime(): - assert_equals(string_to_datetime('2011/05/22 00:24:15 -0700'), - dt(2011, 5, 22, 0, 24, 15)) - - assert_equals(string_to_datetime('2009/04/18 13:04:09 -0700'), - dt(2009, 4, 18, 13, 4, 9)) - #assert_equals(string_to_datetime('2009/11/12 21:15:17 -0800'), - # dt(2009, 11, 12, 21, 15, 17)) - #assert_equals(string_to_datetime('2009/11/12 21:16:20 -0800'), - # dt(2009, 11, 12, 21, 16, 20)) - assert_equals(string_to_datetime('2010/04/17 17:24:29 -0700'), - dt(2010, 4, 17, 17, 24, 29)) - assert_equals(string_to_datetime('2010/05/18 06:10:36 -0700'), - dt(2010, 5, 18, 6, 10, 36)) - assert_equals(string_to_datetime('2010/05/25 21:59:37 -0700'), - dt(2010, 5, 25, 21, 59, 37)) - assert_equals(string_to_datetime('2010/05/26 17:08:41 -0700'), - dt(2010, 5, 26, 17, 8, 41)) - assert_equals(string_to_datetime('2010/06/20 06:13:37 -0700'), - dt(2010, 6, 20, 6, 13, 37)) - assert_equals(string_to_datetime('2010/07/28 12:56:51 -0700'), - dt(2010, 7, 28, 12, 56, 51)) - assert_equals(string_to_datetime('2010/09/20 21:32:49 -0700'), - dt(2010, 9, 20, 21, 32, 49)) + eq_(string_to_datetime('2011/05/22 00:24:15 -0700'), + dt(2011, 5, 22, 0, 24, 15)) + + eq_(string_to_datetime('2009/04/18 13:04:09 -0700'), + dt(2009, 4, 18, 13, 4, 9)) + #eq_(string_to_datetime('2009/11/12 21:15:17 -0800'), + # dt(2009, 11, 12, 21, 15, 17)) + #eq_(string_to_datetime('2009/11/12 21:16:20 -0800'), + # dt(2009, 11, 12, 21, 16, 20)) + eq_(string_to_datetime('2010/04/17 17:24:29 -0700'), + dt(2010, 4, 17, 17, 24, 29)) + eq_(string_to_datetime('2010/05/18 06:10:36 -0700'), + dt(2010, 5, 18, 6, 10, 36)) + eq_(string_to_datetime('2010/05/25 21:59:37 -0700'), + dt(2010, 5, 25, 21, 59, 37)) + eq_(string_to_datetime('2010/05/26 17:08:41 -0700'), + dt(2010, 5, 26, 17, 8, 41)) + eq_(string_to_datetime('2010/06/20 06:13:37 -0700'), + dt(2010, 6, 20, 6, 13, 37)) + eq_(string_to_datetime('2010/07/28 12:56:51 -0700'), + dt(2010, 7, 28, 12, 56, 51)) + eq_(string_to_datetime('2010/09/20 21:32:49 -0700'), + dt(2010, 9, 20, 21, 32, 49)) def test_datetime_to_ghdate(): - assert_equals(datetime_to_ghdate(dt(2011, 5, 22, 0, 24, 15)), - '2011/05/22 00:24:15 -0700') - - assert_equals(datetime_to_ghdate(dt(2009, 4, 18, 20, 4, 9)), - '2009/04/18 20:04:09 -0700') - #assert_equals(datetime_to_ghdate(dt(2009, 11, 13, 4, 15, 17)), - # '2009/11/13 04:15:17 -0800') - #assert_equals(datetime_to_ghdate(dt(2009, 11, 13, 4, 16, 20)), - # '2009/11/13 04:16:20 -0800') - assert_equals(datetime_to_ghdate(dt(2010, 4, 18, 0, 24, 29)), - '2010/04/18 00:24:29 -0700') - assert_equals(datetime_to_ghdate(dt(2010, 5, 18, 13, 10, 36)), - '2010/05/18 13:10:36 -0700') - assert_equals(datetime_to_ghdate(dt(2010, 5, 26, 5, 59, 37)), - '2010/05/26 05:59:37 -0700') - assert_equals(datetime_to_ghdate(dt(2010, 5, 27, 0, 8, 41)), - '2010/05/27 00:08:41 -0700') - assert_equals(datetime_to_ghdate(dt(2010, 6, 20, 13, 13, 37)), - '2010/06/20 13:13:37 -0700') - assert_equals(datetime_to_ghdate(dt(2010, 7, 28, 19, 56, 51)), - '2010/07/28 19:56:51 -0700') - assert_equals(datetime_to_ghdate(dt(2010, 9, 21, 4, 32, 49)), - '2010/09/21 04:32:49 -0700') + eq_(datetime_to_ghdate(dt(2011, 5, 22, 0, 24, 15)), + '2011/05/22 00:24:15 -0700') + + eq_(datetime_to_ghdate(dt(2009, 4, 18, 20, 4, 9)), + '2009/04/18 20:04:09 -0700') + #eq_(datetime_to_ghdate(dt(2009, 11, 13, 4, 15, 17)), + # '2009/11/13 04:15:17 -0800') + #eq_(datetime_to_ghdate(dt(2009, 11, 13, 4, 16, 20)), + # '2009/11/13 04:16:20 -0800') + eq_(datetime_to_ghdate(dt(2010, 4, 18, 0, 24, 29)), + '2010/04/18 00:24:29 -0700') + eq_(datetime_to_ghdate(dt(2010, 5, 18, 13, 10, 36)), + '2010/05/18 13:10:36 -0700') + eq_(datetime_to_ghdate(dt(2010, 5, 26, 5, 59, 37)), + '2010/05/26 05:59:37 -0700') + eq_(datetime_to_ghdate(dt(2010, 5, 27, 0, 8, 41)), + '2010/05/27 00:08:41 -0700') + eq_(datetime_to_ghdate(dt(2010, 6, 20, 13, 13, 37)), + '2010/06/20 13:13:37 -0700') + eq_(datetime_to_ghdate(dt(2010, 7, 28, 19, 56, 51)), + '2010/07/28 19:56:51 -0700') + eq_(datetime_to_ghdate(dt(2010, 9, 21, 4, 32, 49)), + '2010/09/21 04:32:49 -0700') def test_commitdate_to_datetime(): - assert_equals(string_to_datetime('2011-05-22T00:24:15-07:00'), - dt(2011, 5, 22, 0, 24, 15)) - - assert_equals(string_to_datetime('2011-04-09T10:07:30-07:00'), - dt(2011, 4, 9, 10, 7, 30)) - #assert_equals(string_to_datetime('2011-02-19T07:16:11-08:00'), - # dt(2011, 2, 19, 7, 16, 11)) - #assert_equals(string_to_datetime('2010-12-21T12:34:27-08:00'), - # dt(2010, 12, 21, 12, 34, 27)) - assert_equals(string_to_datetime('2011-04-09T10:20:05-07:00'), - dt(2011, 4, 9, 10, 20, 5)) - assert_equals(string_to_datetime('2011-04-09T10:05:58-07:00'), - dt(2011, 4, 9, 10, 5, 58)) - assert_equals(string_to_datetime('2011-04-09T09:53:00-07:00'), - dt(2011, 4, 9, 9, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T10:00:21-07:00'), - dt(2011, 4, 9, 10, 0, 21)) - #assert_equals(string_to_datetime('2010-12-16T15:10:59-08:00'), - # dt(2010, 12, 16, 15, 10, 59)) - assert_equals(string_to_datetime('2011-04-09T09:53:00-07:00'), - dt(2011, 4, 9, 9, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T09:53:00-07:00'), - dt(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-05-22T00:24:15-07:00'), + dt(2011, 5, 22, 0, 24, 15)) + + eq_(string_to_datetime('2011-04-09T10:07:30-07:00'), + dt(2011, 4, 9, 10, 7, 30)) + #eq_(string_to_datetime('2011-02-19T07:16:11-08:00'), + # dt(2011, 2, 19, 7, 16, 11)) + #eq_(string_to_datetime('2010-12-21T12:34:27-08:00'), + # dt(2010, 12, 21, 12, 34, 27)) + eq_(string_to_datetime('2011-04-09T10:20:05-07:00'), + dt(2011, 4, 9, 10, 20, 5)) + eq_(string_to_datetime('2011-04-09T10:05:58-07:00'), + dt(2011, 4, 9, 10, 5, 58)) + eq_(string_to_datetime('2011-04-09T09:53:00-07:00'), + dt(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-04-09T10:00:21-07:00'), + dt(2011, 4, 9, 10, 0, 21)) + #eq_(string_to_datetime('2010-12-16T15:10:59-08:00'), + # dt(2010, 12, 16, 15, 10, 59)) + eq_(string_to_datetime('2011-04-09T09:53:00-07:00'), + dt(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-04-09T09:53:00-07:00'), + dt(2011, 4, 9, 9, 53, 0)) def test_datetime_to_commitdate(): - assert_equals(datetime_to_commitdate(dt(2011, 5, 22, 0, 24, 15)), - '2011-05-22T00:24:15-07:00') - - assert_equals(datetime_to_commitdate(dt(2011, 4, 9, 10, 7, 30)), - '2011-04-09T10:07:30-07:00') - #assert_equals(datetime_to_commitdate(dt(2011, 2, 19, 7, 16, 11)), - # '2011-02-19T07:16:11-08:00') - #assert_equals(datetime_to_commitdate(dt(2010, 12, 21, 12, 34, 27)), - # '2010-12-21T12:34:27-08:00') - assert_equals(datetime_to_commitdate(dt(2011, 4, 9, 10, 20, 5)), - '2011-04-09T10:20:05-07:00') - assert_equals(datetime_to_commitdate(dt(2011, 4, 9, 10, 5, 58)), - '2011-04-09T10:05:58-07:00') - assert_equals(datetime_to_commitdate(dt(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00-07:00') - assert_equals(datetime_to_commitdate(dt(2011, 4, 9, 10, 0, 21)), - '2011-04-09T10:00:21-07:00') - #assert_equals(datetime_to_commitdate(dt(2010, 12, 16, 15, 10, 59)), - # '2010-12-16T15:10:59-08:00') - assert_equals(datetime_to_commitdate(dt(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00-07:00') - assert_equals(datetime_to_commitdate(dt(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00-07:00') + eq_(datetime_to_commitdate(dt(2011, 5, 22, 0, 24, 15)), + '2011-05-22T00:24:15-07:00') + + eq_(datetime_to_commitdate(dt(2011, 4, 9, 10, 7, 30)), + '2011-04-09T10:07:30-07:00') + #eq_(datetime_to_commitdate(dt(2011, 2, 19, 7, 16, 11)), + # '2011-02-19T07:16:11-08:00') + #eq_(datetime_to_commitdate(dt(2010, 12, 21, 12, 34, 27)), + # '2010-12-21T12:34:27-08:00') + eq_(datetime_to_commitdate(dt(2011, 4, 9, 10, 20, 5)), + '2011-04-09T10:20:05-07:00') + eq_(datetime_to_commitdate(dt(2011, 4, 9, 10, 5, 58)), + '2011-04-09T10:05:58-07:00') + eq_(datetime_to_commitdate(dt(2011, 4, 9, 9, 53, 0)), + '2011-04-09T09:53:00-07:00') + eq_(datetime_to_commitdate(dt(2011, 4, 9, 10, 0, 21)), + '2011-04-09T10:00:21-07:00') + #eq_(datetime_to_commitdate(dt(2010, 12, 16, 15, 10, 59)), + # '2010-12-16T15:10:59-08:00') + eq_(datetime_to_commitdate(dt(2011, 4, 9, 9, 53, 0)), + '2011-04-09T09:53:00-07:00') + eq_(datetime_to_commitdate(dt(2011, 4, 9, 9, 53, 0)), + '2011-04-09T09:53:00-07:00') + def test_isodate_to_datetime(): - assert_equals(string_to_datetime('2011-05-22T00:24:15Z'), - dt(2011, 5, 22, 0, 24, 15)) - assert_equals(string_to_datetime('2011-04-09T10:07:30Z'), - dt(2011, 4, 9, 10, 7, 30)) - assert_equals(string_to_datetime('2011-02-19T07:16:11Z'), - dt(2011, 2, 19, 7, 16, 11)) - assert_equals(string_to_datetime('2010-12-21T12:34:27Z'), - dt(2010, 12, 21, 12, 34, 27)) - assert_equals(string_to_datetime('2011-04-09T10:20:05Z'), - dt(2011, 4, 9, 10, 20, 5)) - assert_equals(string_to_datetime('2011-04-09T10:05:58Z'), - dt(2011, 4, 9, 10, 5, 58)) - assert_equals(string_to_datetime('2011-04-09T09:53:00Z'), - dt(2011, 4, 9, 9, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T10:00:21Z'), - dt(2011, 4, 9, 10, 0, 21)) - assert_equals(string_to_datetime('2010-12-16T15:10:59Z'), - dt(2010, 12, 16, 15, 10, 59)) - assert_equals(string_to_datetime('2011-04-09T09:53:00Z'), - dt(2011, 4, 9, 9, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T09:53:00Z'), - dt(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-05-22T00:24:15Z'), dt(2011, 5, 22, 0, 24, 15)) + eq_(string_to_datetime('2011-04-09T10:07:30Z'), dt(2011, 4, 9, 10, 7, 30)) + eq_(string_to_datetime('2011-02-19T07:16:11Z'), dt(2011, 2, 19, 7, 16, 11)) + eq_(string_to_datetime('2010-12-21T12:34:27Z'), + dt(2010, 12, 21, 12, 34, 27)) + eq_(string_to_datetime('2011-04-09T10:20:05Z'), dt(2011, 4, 9, 10, 20, 5)) + eq_(string_to_datetime('2011-04-09T10:05:58Z'), dt(2011, 4, 9, 10, 5, 58)) + eq_(string_to_datetime('2011-04-09T09:53:00Z'), dt(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-04-09T10:00:21Z'), dt(2011, 4, 9, 10, 0, 21)) + eq_(string_to_datetime('2010-12-16T15:10:59Z'), + dt(2010, 12, 16, 15, 10, 59)) + eq_(string_to_datetime('2011-04-09T09:53:00Z'), dt(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-04-09T09:53:00Z'), dt(2011, 4, 9, 9, 53, 0)) def test_datetime_to_isodate(): - assert_equals(datetime_to_isodate(dt(2011, 5, 22, 0, 24, 15)), - '2011-05-22T00:24:15Z') - assert_equals(datetime_to_isodate(dt(2011, 4, 9, 10, 7, 30)), - '2011-04-09T10:07:30Z') - assert_equals(datetime_to_isodate(dt(2011, 2, 19, 7, 16, 11)), - '2011-02-19T07:16:11Z') - assert_equals(datetime_to_isodate(dt(2010, 12, 21, 12, 34, 27)), - '2010-12-21T12:34:27Z') - assert_equals(datetime_to_isodate(dt(2011, 4, 9, 10, 20, 5)), - '2011-04-09T10:20:05Z') - assert_equals(datetime_to_isodate(dt(2011, 4, 9, 10, 5, 58)), - '2011-04-09T10:05:58Z') - assert_equals(datetime_to_isodate(dt(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00Z') - assert_equals(datetime_to_isodate(dt(2011, 4, 9, 10, 0, 21)), - '2011-04-09T10:00:21Z') - assert_equals(datetime_to_isodate(dt(2010, 12, 16, 15, 10, 59)), - '2010-12-16T15:10:59Z') - assert_equals(datetime_to_isodate(dt(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00Z') - assert_equals(datetime_to_isodate(dt(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00Z') + eq_(datetime_to_isodate(dt(2011, 5, 22, 0, 24, 15)), + '2011-05-22T00:24:15Z') + eq_(datetime_to_isodate(dt(2011, 4, 9, 10, 7, 30)), '2011-04-09T10:07:30Z') + eq_(datetime_to_isodate(dt(2011, 2, 19, 7, 16, 11)), + '2011-02-19T07:16:11Z') + eq_(datetime_to_isodate(dt(2010, 12, 21, 12, 34, 27)), + '2010-12-21T12:34:27Z') + eq_(datetime_to_isodate(dt(2011, 4, 9, 10, 20, 5)), '2011-04-09T10:20:05Z') + eq_(datetime_to_isodate(dt(2011, 4, 9, 10, 5, 58)), '2011-04-09T10:05:58Z') + eq_(datetime_to_isodate(dt(2011, 4, 9, 9, 53, 0)), '2011-04-09T09:53:00Z') + eq_(datetime_to_isodate(dt(2011, 4, 9, 10, 0, 21)), '2011-04-09T10:00:21Z') + eq_(datetime_to_isodate(dt(2010, 12, 16, 15, 10, 59)), + '2010-12-16T15:10:59Z') + eq_(datetime_to_isodate(dt(2011, 4, 9, 9, 53, 0)), '2011-04-09T09:53:00Z') + eq_(datetime_to_isodate(dt(2011, 4, 9, 9, 53, 0)), '2011-04-09T09:53:00Z') diff --git a/tests/test_issues.py b/tests/test_issues.py index 83e2822..67cbff7 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -1,41 +1,82 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. -from nose.tools import assert_equals +from datetime import datetime + +from nose.tools import eq_ import utils -class ReprTests(utils.HttpMockTestCase): +class Issue(utils.HttpMockTestCase): + def test_properties(self): + issue = self.client.issues.show('ask/python-github2', 24) + eq_(issue.position, 24.0) + eq_(issue.number, 24) + eq_(issue.votes, 0) + eq_(len(issue.body), 164) + eq_(issue.title, 'Pagination support for commits.') + eq_(issue.user, 'svetlyak40wt') + eq_(issue.state, 'open') + eq_(issue.labels, []) + eq_(issue.created_at, datetime(2010, 12, 8, 23, 50, 26)) + eq_(issue.closed_at, None) + eq_(issue.updated_at, datetime(2011, 1, 4, 16, 26, 7)) + eq_(issue.diff_url, + 'https://github.com/ask/python-github2/pull/24.diff') + eq_(issue.patch_url, + 'https://github.com/ask/python-github2/pull/24.patch') + eq_(issue.pull_request_url, + 'https://github.com/ask/python-github2/pull/24') + def test_issue_repr(self): issue = self.client.issues.show('ask/python-github2', 24) - assert_equals(repr(issue), - '') + eq_(repr(issue), '') + + +class Comment(utils.HttpMockTestCase): + def test_properties(self): + comments = self.client.issues.comments('ask/python-github2', 24) + comment = comments[0] + eq_(comment.created_at, datetime(2010, 12, 9, 22, 37, 26)) + eq_(comment.updated_at, datetime(2010, 12, 9, 22, 37, 26)) + eq_(len(comment.body), 267) + eq_(comment.id, 601871) + eq_(comment.user, 'nvie') def test_comment_repr(self): comments = self.client.issues.comments('ask/python-github2', 24) - assert_equals(repr(comments[1]), - '') + eq_(repr(comments[1]), '') class IssueQueries(utils.HttpMockTestCase): - """Test issue querying""" + + """Test issue querying.""" + def test_search(self): issues = self.client.issues.search('ask/python-github2', 'timezone', 'closed') - assert_equals(len(issues), 2) - assert_equals(issues[1].number, 39) + eq_(len(issues), 2) + eq_(issues[1].number, 39) def test_list(self): issues = self.client.issues.list('ask/python-github2') - assert_equals(len(issues), 4) - assert_equals(issues[-1].number, 58) + eq_(len(issues), 4) + eq_(issues[-1].number, 58) def test_list_with_state(self): issues = self.client.issues.list('ask/python-github2', "closed") - assert_equals(len(issues), 55) - assert_equals(issues[0].number, 59) + eq_(len(issues), 55) + eq_(issues[0].number, 59) def test_issue_labels(self): labels = self.client.issues.list_labels('JNRowe/misc-overlay') - assert_equals(len(labels), 4) - assert_equals(labels[0], 'feature') + eq_(len(labels), 4) + eq_(labels[0], 'feature') + + def test_list_by_label(self): + issues = self.client.issues.list_by_label('JNRowe/misc-overlay', 'bug') + eq_(len(issues), 30) + eq_(issues[-1].number, 328) diff --git a/tests/test_organizations.py b/tests/test_organizations.py index 1425a07..e2ddc77 100644 --- a/tests/test_organizations.py +++ b/tests/test_organizations.py @@ -1,33 +1,72 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. -from nose.tools import (assert_equals, assert_true) +from datetime import datetime + +from nose.tools import (eq_, ok_) import utils class OrganizationProperties(utils.HttpMockTestCase): + def test_properties(self): + organization = self.client.organizations.show('github') + eq_(organization.id, 9919) + eq_(organization.name, 'GitHub') + eq_(organization.blog, 'https://github.com/about') + eq_(organization.location, 'San Francisco, CA') + eq_(organization.gravatar_id, '61024896f291303615bcd4f7a0dcfb74') + eq_(organization.login, 'github') + eq_(organization.email, 'support@github.com') + eq_(organization.company, None) + eq_(organization.created_at, datetime(2008, 5, 10, 21, 37, 31)) + eq_(organization.following_count, 0) + eq_(organization.followers_count, 591) + eq_(organization.public_gist_count, 0) + eq_(organization.public_repo_count, 31) + eq_(organization.permission, None) + eq_(organization.plan, None) + def test_is_authenticated(self): organization = self.client.organizations.show('github') - assert_true(organization.is_authenticated() is False) + ok_(organization.is_authenticated() is False) organization = self.client.organizations.show('fake_org_with_auth') - assert_true(organization.is_authenticated() is True) + ok_(organization.is_authenticated() is True) class Organization(utils.HttpMockTestCase): def test_repr(self): organization = self.client.organizations.show('github') - assert_equals(repr(organization), - '') + eq_(repr(organization), '') class OrganizationQueries(utils.HttpMockTestCase): """Test organisation querying""" def test_public_repositories(self): repos = self.client.organizations.public_repositories('github') - assert_equals(len(repos), 31) - assert_equals(repos[2].name, 'hubahuba') + eq_(len(repos), 31) + eq_(repos[2].name, 'hubahuba') def test_public_members(self): members = self.client.organizations.public_members('github') - assert_equals(len(members), 35) - assert_equals(members[2].name, 'Ben Burkert') + eq_(len(members), 35) + eq_(members[2].name, 'Ben Burkert') + + +class OrganizationsEdits(utils.HttpMockAuthenticatedTestCase): + def test_add_team(self): + team = self.client.organizations.add_team('JNRowe-test-org', + 'test_pull', 'pull') + eq_(team.name, 'team_pull') + eq_(team.permission, 'pull') + + def test_add_team_with_repos(self): + projects = ['JNRowe-test-org/test1', 'JNRowe-test-org/test2'] + team = self.client.organizations.add_team('JNRowe-test-org', + 'test_push', 'push', + projects) + + team_repos = self.client.teams.repositories(team.id) + eq_(['/'.join([x.organization, x.name]) for x in team_repos], projects) diff --git a/tests/test_pull_requests.py b/tests/test_pull_requests.py index 80cc540..efe486c 100644 --- a/tests/test_pull_requests.py +++ b/tests/test_pull_requests.py @@ -1,26 +1,60 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. -from nose.tools import assert_equals +from datetime import datetime + +from nose.tools import eq_ import utils class PullRequest(utils.HttpMockTestCase): + def test_properties(self): + pull_request = self.client.pull_requests.show('ask/python-github2', 39) + eq_(pull_request.state, 'closed') + eq_(pull_request.base['sha'], + '0786a96c80afad7bbd0747df590f649eaa46ca04') + eq_(pull_request.head['sha'], + '5438e41d9c390f53089ed3fa0842831fafc73d8e') + eq_(pull_request.issue_user['login'], 'JNRowe') + eq_(pull_request.user['login'], 'JNRowe') + eq_(pull_request.title, 'Datetime timezone handling.') + eq_(len(pull_request.body), 1442) + eq_(pull_request.position, 39.0) + eq_(pull_request.number, 39.0) + eq_(pull_request.votes, 0) + eq_(pull_request.comments, 4) + eq_(pull_request.diff_url, + 'https://github.com/ask/python-github2/pull/39.diff') + eq_(pull_request.patch_url, + 'https://github.com/ask/python-github2/pull/39.patch') + eq_(pull_request.labels, []) + eq_(pull_request.html_url, + 'https://github.com/ask/python-github2/pull/39') + eq_(pull_request.issue_created_at, datetime(2011, 4, 18, 15, 25, 47)) + eq_(pull_request.issue_updated_at, datetime(2011, 6, 23, 9, 33, 57)) + eq_(pull_request.created_at, datetime(2011, 6, 20, 16, 51, 24)) + eq_(pull_request.updated_at, datetime(2011, 6, 23, 9, 28, 42)) + eq_(pull_request.closed_at, datetime(2011, 6, 23, 9, 33, 57)) + eq_(len(pull_request.discussion), 13) + eq_(pull_request.mergeable, True) + def test_repr(self): pull_request = self.client.pull_requests.show('ask/python-github2', 39) - assert_equals(repr(pull_request), - '') + eq_(repr(pull_request), '') class PullRequestQueries(utils.HttpMockTestCase): """Test pull request querying""" def test_list(self): pull_requests = self.client.pull_requests.list('ask/python-github2') - assert_equals(len(pull_requests), 1) - assert_equals(pull_requests[0].title, 'Pagination support for commits.') + eq_(len(pull_requests), 1) + eq_(pull_requests[0].title, 'Pagination support for commits.') def test_list_with_page(self): pull_requests = self.client.pull_requests.list('robbyrussell/oh-my-zsh', page=2) - assert_equals(len(pull_requests), 52) - assert_equals(pull_requests[1].title, 'Added my own custom theme') + eq_(len(pull_requests), 52) + eq_(pull_requests[1].title, 'Added my own custom theme') diff --git a/tests/test_regression.py b/tests/test_regression.py index 5438888..b5fee60 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -1,25 +1,22 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. import httplib2 -from nose.tools import assert_equals +from nose.tools import eq_ from github2.client import Github -import utils - def test_issue_50(): - """Erroneous init of ``Http`` with proxy setup + """Erroneous init of ``Http`` with proxy setup. See https://github.com/ask/python-github2/pull/50 """ - utils.set_http_mock() - client = Github(proxy_host="my.proxy.com", proxy_port=9000) - setup_args = client.request._http.called_with - assert_equals(type(setup_args['proxy_info']), httplib2.ProxyInfo) - assert_equals(setup_args['proxy_info'].proxy_host, 'my.proxy.com') - assert_equals(setup_args['proxy_info'].proxy_port, 9000) - - utils.unset_http_mock() + proxy_info = client.request._http.proxy_info + eq_(type(proxy_info), httplib2.ProxyInfo) + eq_(proxy_info.proxy_host, 'my.proxy.com') + eq_(proxy_info.proxy_port, 9000) diff --git a/tests/test_repositories.py b/tests/test_repositories.py index d7f9c07..8abbc30 100644 --- a/tests/test_repositories.py +++ b/tests/test_repositories.py @@ -1,93 +1,122 @@ -import _setup +# coding: utf-8 +# Copyright (C) 2011-2012 James Rowe +# Stéphane Angel +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. import datetime -from nose.tools import assert_equals +from nose.tools import eq_ -from github2.client import Github import utils class Repo(utils.HttpMockTestCase): def test_repr(self): repo = self.client.repos.show('JNRowe/misc-overlay') - assert_equals(repr(repo), '') + eq_(repr(repo), '') class RepoProperties(utils.HttpMockTestCase): - """Test repository property handling""" + + """Test repository property handling.""" + def test_repo(self): repo = self.client.repos.show('JNRowe/misc-overlay') - assert_equals(repo.name, 'misc-overlay') - assert_equals(repo.description, - 'Gentoo overlay -- miscellaneous packages') - assert_equals(repo.url, 'https://github.com/JNRowe/misc-overlay') - assert_equals(repo.owner, 'JNRowe') - assert_equals(repo.homepage, 'http://jnrowe.github.com/misc-overlay/') + eq_(repo.name, 'misc-overlay') + eq_(repo.description, 'Gentoo overlay -- miscellaneous packages') + eq_(repo.url, 'https://github.com/JNRowe/misc-overlay') + eq_(repo.owner, 'JNRowe') + eq_(repo.homepage, 'http://jnrowe.github.com/misc-overlay/') - assert_equals(repo.project, 'JNRowe/misc-overlay') + eq_(repo.project, 'JNRowe/misc-overlay') def test_meta(self): repo = self.client.repos.show('JNRowe/misc-overlay') - assert_equals(repo.forks, 0) - assert_equals(repo.watchers, 5) - assert_equals(repo.private, False) - assert_equals(repo.fork, False) - assert_equals(repo.master_branch, None) - assert_equals(repo.integration_branch, None) - assert_equals(repo.open_issues, 13) - assert_equals(repo.created_at, - datetime.datetime(2009, 5, 2, 7, 32, 50)) - assert_equals(repo.pushed_at, - datetime.datetime(2011, 8, 11, 11, 46, 23)) - assert_equals(repo.has_downloads, True) - assert_equals(repo.has_wiki, True) - assert_equals(repo.has_issues, True) - assert_equals(repo.language, 'Python') + eq_(repo.forks, 0) + eq_(repo.watchers, 5) + eq_(repo.private, False) + eq_(repo.fork, False) + eq_(repo.master_branch, None) + eq_(repo.integration_branch, None) + eq_(repo.open_issues, 13) + eq_(repo.created_at, datetime.datetime(2009, 5, 2, 7, 32, 50)) + eq_(repo.pushed_at, datetime.datetime(2011, 8, 11, 11, 46, 23)) + eq_(repo.has_downloads, True) + eq_(repo.has_wiki, True) + eq_(repo.has_issues, True) + eq_(repo.language, 'Python') def test_fork_properties(self): repo = self.client.repos.show('JNRowe/python-github2') - assert_equals(repo.forks, 0) - assert_equals(repo.fork, True) - assert_equals(repo.parent, 'ask/python-github2') + eq_(repo.forks, 0) + eq_(repo.fork, True) + eq_(repo.parent, 'ask/python-github2') + eq_(repo.source, 'ask/python-github2') class RepoQueries(utils.HttpMockTestCase): """Test repository querying""" def test_search(self): repos = self.client.repos.search('surfraw') - assert_equals(len(repos), 8) - assert_equals(repos[0].owner, 'JNRowe') + eq_(len(repos), 8) + eq_(repos[0].owner, 'JNRowe') def test_list(self): repos = self.client.repos.list('JNRowe') - assert_equals(len(repos), 48) - assert_equals(repos[0].name, 'bfm') + eq_(len(repos), 48) + eq_(repos[0].name, 'bfm') def test_list_with_page(self): repos = self.client.repos.list('tekkub', page=2) - assert_equals(len(repos), 37) - assert_equals(repos[0].name, 'OhSnap') + eq_(len(repos), 37) + eq_(repos[0].name, 'OhSnap') def test_watching(self): repos = self.client.repos.watching('JNRowe') - assert_equals(len(repos), 90) - assert_equals(repos[0].name, 'nerdtree') + eq_(len(repos), 90) + eq_(repos[0].name, 'nerdtree') def test_watching_with_page(self): repos = self.client.repos.watching('tekkub', page=2) - assert_equals(len(repos), 39) - assert_equals(repos[0].name, 'Buffoon') + eq_(len(repos), 39) + eq_(repos[0].name, 'Buffoon') def test_contributors(self): contributors = self.client.repos.list_contributors('ask/python-github2') - assert_equals(len(contributors), 29) - assert_equals(contributors[1].name, 'Ask Solem Hoel') + eq_(len(contributors), 29) + eq_(contributors[1].name, 'Ask Solem Hoel') + + def test_list_collaborators(self): + collaborators = self.client.repos.list_collaborators('ask/python-github2') + eq_(len(collaborators), 4) + eq_(collaborators[2], 'JNRowe') + + def test_languages(self): + languages = self.client.repos.languages('JNRowe/misc-overlay') + eq_(len(languages), 2) + eq_(languages['Python'], 11194) + + def test_tags(self): + tags = self.client.repos.tags('ask/python-github2') + eq_(len(tags), 7) + eq_(tags['0.4.1'], '96b0a41dd249c521323700bc11a0a721a7c9e642') + + def test_branches(self): + branches = self.client.repos.branches('ask/python-github2') + eq_(len(branches), 1) + eq_(branches['master'], '1c83cde9b5a7c396a01af1007fb7b88765b9ae45') + + def test_watchers(self): + watchers = self.client.repos.watchers('ask/python-github2') + eq_(len(watchers), 143) + eq_(watchers[0], 'ask') class AuthenticatedRepoQueries(utils.HttpMockAuthenticatedTestCase): def test_pushable(self): repos = self.client.repos.pushable() - assert_equals(len(repos), 1) - assert_equals(repos[0].name, 'python-github2') + eq_(len(repos), 1) + eq_(repos[0].name, 'python-github2') diff --git a/tests/test_request.py b/tests/test_request.py index a36086f..529de26 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,33 +1,93 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. import unittest -from nose.tools import (assert_equals, assert_true) +try: + from urllib.parse import parse_qs # For Python 3 +except ImportError: + try: + from urlparse import parse_qs # NOQA + except ImportError: # For Python <2.6 + from cgi import parse_qs # NOQA + +try: + from nose.tools import (assert_dict_contains_subset, assert_dict_equal) +except ImportError: # for Python <2.7 + import unittest2 + + _binding = unittest2.TestCase('run') + assert_dict_contains_subset = _binding.assertDictContainsSubset # NOQA + assert_dict_equal = _binding.assertDictEqual # NOQA + from github2 import request +def assert_params(first, second): + assert_dict_equal(first, parse_qs(second)) + + +def assert_params_contain(first, second): + assert_dict_contains_subset(first, parse_qs(second)) + + class TestAuthEncode(unittest.TestCase): - """Test processing of authentication data""" + + """Test processing of authentication data.""" + def setUp(self): self.r = request.GithubRequest() def test_unauthenticated(self): - assert_equals('', self.r.encode_authentication_data({})) + assert_params({}, self.r.encode_authentication_data({})) def test_access_token(self): - self.r.access_token = 'hex string' - assert_equals('access_token=hex+string', - self.r.encode_authentication_data({})) - assert_true('access_token=hex+string' in - self.r.encode_authentication_data({'key': 'value'})) - self.r.access_token = None + try: + self.r.access_token = 'hex string' + assert_params({'access_token': ['hex string', ]}, + self.r.encode_authentication_data({})) + finally: + self.r.access_token = None def test_user_token(self): - self.r.username = 'user' - self.r.api_token = 'hex string' - assert_equals('login=user&token=hex+string', - self.r.encode_authentication_data({})) - assert_true('login=user&token=hex+string' in - self.r.encode_authentication_data({'key': 'value'})) - self.r.username = self.r.api_token = None + try: + self.r.username = 'user' + self.r.api_token = 'hex string' + token_params = {'login': ['user', ], 'token': ['hex string', ]} + assert_params(token_params, self.r.encode_authentication_data({})) + finally: + self.r.username = self.r.api_token = None + + +class TestParameterEncoding(unittest.TestCase): + def setUp(self): + self.r = request.GithubRequest() + self.params = { + 'key1': 'value1', + 'key2': 'value2', + } + + def test_no_parameters(self): + assert_params({}, self.r.encode_authentication_data({})) + + def test_parameters(self): + assert_params({'key1': ['value1', ], 'key2': ['value2', ]}, + self.r.encode_authentication_data(self.params)) + + def test_parameters_with_auth(self): + try: + self.r.username = 'user' + self.r.api_token = 'hex string' + assert_params({'key2': ['value2', ], 'login': ['user', ], + 'token': ['hex string', ], 'key1': ['value1', ]}, + self.r.encode_authentication_data(self.params)) + finally: + self.r.username = '' + self.r.api_token = '' + + def test_multivalue_parameters(self): + multivals = {'key': ['value1', 'value2']} + assert_params(multivals, self.r.encode_authentication_data(multivals)) diff --git a/tests/test_teams.py b/tests/test_teams.py new file mode 100644 index 0000000..cd18166 --- /dev/null +++ b/tests/test_teams.py @@ -0,0 +1,14 @@ +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. + +from nose.tools import eq_ + +import utils + + +class TeamEdits(utils.HttpMockAuthenticatedTestCase): + def test_add_member(self): + users = self.client.teams.add_member(121990, 'JNRowe') + eq_(users[0].login, 'JNRowe') diff --git a/tests/test_tz_aware_date_handling.py b/tests/test_tz_aware_date_handling.py index c2e07c6..c2b152f 100644 --- a/tests/test_tz_aware_date_handling.py +++ b/tests/test_tz_aware_date_handling.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- - -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. from datetime import datetime as dt from dateutil.tz import tzutc -from nose.tools import assert_equals +from nose.tools import eq_ from github2 import core from github2.core import (datetime_to_ghdate, datetime_to_commitdate, @@ -13,168 +15,169 @@ def setup_module(): - """Enable timezone-aware datetime handling for this module's tests""" + """Enable timezone-aware datetime handling for this module's tests.""" core.NAIVE = False def teardown_module(): - """Disable timezone-aware datetime handling when finished with this module""" + """Disable timezone-aware datetime handling when tests have completed.""" core.NAIVE = True def dt_utz(year, month, day, hour, minute, second): - """Produce a UTC-anchored datetime object + """Produce a UTC-anchored datetime object. + + :see: :class:`datetime.datetime` - :see: ``datetime.datetime`` """ return dt(year, month, day, hour, minute, second, tzinfo=tzutc()) def test_ghdate_to_datetime(): - assert_equals(string_to_datetime('2011/05/22 00:24:15 -0700'), - dt_utz(2011, 5, 22, 7, 24, 15)) - assert_equals(string_to_datetime('2009/04/18 13:04:09 -0700'), - dt_utz(2009, 4, 18, 20, 4, 9)) - assert_equals(string_to_datetime('2009/11/12 21:15:17 -0800'), - dt_utz(2009, 11, 13, 5, 15, 17)) - assert_equals(string_to_datetime('2009/11/12 21:16:20 -0800'), - dt_utz(2009, 11, 13, 5, 16, 20)) - assert_equals(string_to_datetime('2010/04/17 17:24:29 -0700'), - dt_utz(2010, 4, 18, 0, 24, 29)) - assert_equals(string_to_datetime('2010/05/18 06:10:36 -0700'), - dt_utz(2010, 5, 18, 13, 10, 36)) - assert_equals(string_to_datetime('2010/05/25 21:59:37 -0700'), - dt_utz(2010, 5, 26, 4, 59, 37)) - assert_equals(string_to_datetime('2010/05/26 17:08:41 -0700'), - dt_utz(2010, 5, 27, 0, 8, 41)) - assert_equals(string_to_datetime('2010/06/20 06:13:37 -0700'), - dt_utz(2010, 6, 20, 13, 13, 37)) - assert_equals(string_to_datetime('2010/07/28 12:56:51 -0700'), - dt_utz(2010, 7, 28, 19, 56, 51)) - assert_equals(string_to_datetime('2010/09/20 21:32:49 -0700'), - dt_utz(2010, 9, 21, 4, 32, 49)) + eq_(string_to_datetime('2011/05/22 00:24:15 -0700'), + dt_utz(2011, 5, 22, 7, 24, 15)) + eq_(string_to_datetime('2009/04/18 13:04:09 -0700'), + dt_utz(2009, 4, 18, 20, 4, 9)) + eq_(string_to_datetime('2009/11/12 21:15:17 -0800'), + dt_utz(2009, 11, 13, 5, 15, 17)) + eq_(string_to_datetime('2009/11/12 21:16:20 -0800'), + dt_utz(2009, 11, 13, 5, 16, 20)) + eq_(string_to_datetime('2010/04/17 17:24:29 -0700'), + dt_utz(2010, 4, 18, 0, 24, 29)) + eq_(string_to_datetime('2010/05/18 06:10:36 -0700'), + dt_utz(2010, 5, 18, 13, 10, 36)) + eq_(string_to_datetime('2010/05/25 21:59:37 -0700'), + dt_utz(2010, 5, 26, 4, 59, 37)) + eq_(string_to_datetime('2010/05/26 17:08:41 -0700'), + dt_utz(2010, 5, 27, 0, 8, 41)) + eq_(string_to_datetime('2010/06/20 06:13:37 -0700'), + dt_utz(2010, 6, 20, 13, 13, 37)) + eq_(string_to_datetime('2010/07/28 12:56:51 -0700'), + dt_utz(2010, 7, 28, 19, 56, 51)) + eq_(string_to_datetime('2010/09/20 21:32:49 -0700'), + dt_utz(2010, 9, 21, 4, 32, 49)) def test_datetime_to_ghdate(): - assert_equals(datetime_to_ghdate(dt_utz(2011, 5, 22, 7, 24, 15)), - '2011/05/22 00:24:15 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2009, 4, 18, 20, 4, 9)), - '2009/04/18 13:04:09 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2009, 11, 13, 4, 15, 17)), - '2009/11/12 20:15:17 -0800') - assert_equals(datetime_to_ghdate(dt_utz(2009, 11, 13, 4, 16, 20)), - '2009/11/12 20:16:20 -0800') - assert_equals(datetime_to_ghdate(dt_utz(2010, 4, 18, 0, 24, 29)), - '2010/04/17 17:24:29 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2010, 5, 18, 13, 10, 36)), - '2010/05/18 06:10:36 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2010, 5, 26, 5, 59, 37)), - '2010/05/25 22:59:37 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2010, 5, 27, 0, 8, 41)), - '2010/05/26 17:08:41 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2010, 6, 20, 13, 13, 37)), - '2010/06/20 06:13:37 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2010, 7, 28, 19, 56, 51)), - '2010/07/28 12:56:51 -0700') - assert_equals(datetime_to_ghdate(dt_utz(2010, 9, 21, 4, 32, 49)), - '2010/09/20 21:32:49 -0700') + eq_(datetime_to_ghdate(dt_utz(2011, 5, 22, 7, 24, 15)), + '2011/05/22 00:24:15 -0700') + eq_(datetime_to_ghdate(dt_utz(2009, 4, 18, 20, 4, 9)), + '2009/04/18 13:04:09 -0700') + eq_(datetime_to_ghdate(dt_utz(2009, 11, 13, 4, 15, 17)), + '2009/11/12 20:15:17 -0800') + eq_(datetime_to_ghdate(dt_utz(2009, 11, 13, 4, 16, 20)), + '2009/11/12 20:16:20 -0800') + eq_(datetime_to_ghdate(dt_utz(2010, 4, 18, 0, 24, 29)), + '2010/04/17 17:24:29 -0700') + eq_(datetime_to_ghdate(dt_utz(2010, 5, 18, 13, 10, 36)), + '2010/05/18 06:10:36 -0700') + eq_(datetime_to_ghdate(dt_utz(2010, 5, 26, 5, 59, 37)), + '2010/05/25 22:59:37 -0700') + eq_(datetime_to_ghdate(dt_utz(2010, 5, 27, 0, 8, 41)), + '2010/05/26 17:08:41 -0700') + eq_(datetime_to_ghdate(dt_utz(2010, 6, 20, 13, 13, 37)), + '2010/06/20 06:13:37 -0700') + eq_(datetime_to_ghdate(dt_utz(2010, 7, 28, 19, 56, 51)), + '2010/07/28 12:56:51 -0700') + eq_(datetime_to_ghdate(dt_utz(2010, 9, 21, 4, 32, 49)), + '2010/09/20 21:32:49 -0700') def test_commitdate_to_datetime(): - assert_equals(string_to_datetime('2011-05-22T00:24:15-07:00'), - dt_utz(2011, 5, 22, 7, 24, 15)) - assert_equals(string_to_datetime('2011-04-09T10:07:30-07:00'), - dt_utz(2011, 4, 9, 17, 7, 30)) - assert_equals(string_to_datetime('2011-02-19T07:16:11-08:00'), - dt_utz(2011, 2, 19, 15, 16, 11)) - assert_equals(string_to_datetime('2010-12-21T12:34:27-08:00'), - dt_utz(2010, 12, 21, 20, 34, 27)) - assert_equals(string_to_datetime('2011-04-09T10:20:05-07:00'), - dt_utz(2011, 4, 9, 17, 20, 5)) - assert_equals(string_to_datetime('2011-04-09T10:05:58-07:00'), - dt_utz(2011, 4, 9, 17, 5, 58)) - assert_equals(string_to_datetime('2011-04-09T09:53:00-07:00'), - dt_utz(2011, 4, 9, 16, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T10:00:21-07:00'), - dt_utz(2011, 4, 9, 17, 0, 21)) - assert_equals(string_to_datetime('2010-12-16T15:10:59-08:00'), - dt_utz(2010, 12, 16, 23, 10, 59)) - assert_equals(string_to_datetime('2011-04-09T09:53:00-07:00'), - dt_utz(2011, 4, 9, 16, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T09:53:00-07:00'), - dt_utz(2011, 4, 9, 16, 53, 0)) + eq_(string_to_datetime('2011-05-22T00:24:15-07:00'), + dt_utz(2011, 5, 22, 7, 24, 15)) + eq_(string_to_datetime('2011-04-09T10:07:30-07:00'), + dt_utz(2011, 4, 9, 17, 7, 30)) + eq_(string_to_datetime('2011-02-19T07:16:11-08:00'), + dt_utz(2011, 2, 19, 15, 16, 11)) + eq_(string_to_datetime('2010-12-21T12:34:27-08:00'), + dt_utz(2010, 12, 21, 20, 34, 27)) + eq_(string_to_datetime('2011-04-09T10:20:05-07:00'), + dt_utz(2011, 4, 9, 17, 20, 5)) + eq_(string_to_datetime('2011-04-09T10:05:58-07:00'), + dt_utz(2011, 4, 9, 17, 5, 58)) + eq_(string_to_datetime('2011-04-09T09:53:00-07:00'), + dt_utz(2011, 4, 9, 16, 53, 0)) + eq_(string_to_datetime('2011-04-09T10:00:21-07:00'), + dt_utz(2011, 4, 9, 17, 0, 21)) + eq_(string_to_datetime('2010-12-16T15:10:59-08:00'), + dt_utz(2010, 12, 16, 23, 10, 59)) + eq_(string_to_datetime('2011-04-09T09:53:00-07:00'), + dt_utz(2011, 4, 9, 16, 53, 0)) + eq_(string_to_datetime('2011-04-09T09:53:00-07:00'), + dt_utz(2011, 4, 9, 16, 53, 0)) def test_datetime_to_commitdate(): - assert_equals(datetime_to_commitdate(dt_utz(2011, 5, 22, 7, 24, 15)), - '2011-05-22T00:24:15-07:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 7, 30)), - '2011-04-09T10:07:30-07:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 2, 19, 15, 16, 11)), - '2011-02-19T07:16:11-08:00') - assert_equals(datetime_to_commitdate(dt_utz(2010, 12, 21, 20, 34, 27)), - '2010-12-21T12:34:27-08:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 20, 5)), - '2011-04-09T10:20:05-07:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 5, 58)), - '2011-04-09T10:05:58-07:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 4, 9, 16, 53, 0)), - '2011-04-09T09:53:00-07:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 0, 21)), - '2011-04-09T10:00:21-07:00') - assert_equals(datetime_to_commitdate(dt_utz(2010, 12, 16, 23, 10, 59)), - '2010-12-16T15:10:59-08:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 4, 9, 16, 53, 0)), - '2011-04-09T09:53:00-07:00') - assert_equals(datetime_to_commitdate(dt_utz(2011, 4, 9, 16, 53, 0)), - '2011-04-09T09:53:00-07:00') + eq_(datetime_to_commitdate(dt_utz(2011, 5, 22, 7, 24, 15)), + '2011-05-22T00:24:15-07:00') + eq_(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 7, 30)), + '2011-04-09T10:07:30-07:00') + eq_(datetime_to_commitdate(dt_utz(2011, 2, 19, 15, 16, 11)), + '2011-02-19T07:16:11-08:00') + eq_(datetime_to_commitdate(dt_utz(2010, 12, 21, 20, 34, 27)), + '2010-12-21T12:34:27-08:00') + eq_(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 20, 5)), + '2011-04-09T10:20:05-07:00') + eq_(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 5, 58)), + '2011-04-09T10:05:58-07:00') + eq_(datetime_to_commitdate(dt_utz(2011, 4, 9, 16, 53, 0)), + '2011-04-09T09:53:00-07:00') + eq_(datetime_to_commitdate(dt_utz(2011, 4, 9, 17, 0, 21)), + '2011-04-09T10:00:21-07:00') + eq_(datetime_to_commitdate(dt_utz(2010, 12, 16, 23, 10, 59)), + '2010-12-16T15:10:59-08:00') + eq_(datetime_to_commitdate(dt_utz(2011, 4, 9, 16, 53, 0)), + '2011-04-09T09:53:00-07:00') + eq_(datetime_to_commitdate(dt_utz(2011, 4, 9, 16, 53, 0)), + '2011-04-09T09:53:00-07:00') def test_isodate_to_datetime(): - assert_equals(string_to_datetime('2011-05-22T00:24:15Z'), - dt_utz(2011, 5, 22, 0, 24, 15)) - assert_equals(string_to_datetime('2011-04-09T10:07:30Z'), - dt_utz(2011, 4, 9, 10, 7, 30)) - assert_equals(string_to_datetime('2011-02-19T07:16:11Z'), - dt_utz(2011, 2, 19, 7, 16, 11)) - assert_equals(string_to_datetime('2010-12-21T12:34:27Z'), - dt_utz(2010, 12, 21, 12, 34, 27)) - assert_equals(string_to_datetime('2011-04-09T10:20:05Z'), - dt_utz(2011, 4, 9, 10, 20, 5)) - assert_equals(string_to_datetime('2011-04-09T10:05:58Z'), - dt_utz(2011, 4, 9, 10, 5, 58)) - assert_equals(string_to_datetime('2011-04-09T09:53:00Z'), - dt_utz(2011, 4, 9, 9, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T10:00:21Z'), - dt_utz(2011, 4, 9, 10, 0, 21)) - assert_equals(string_to_datetime('2010-12-16T15:10:59Z'), - dt_utz(2010, 12, 16, 15, 10, 59)) - assert_equals(string_to_datetime('2011-04-09T09:53:00Z'), - dt_utz(2011, 4, 9, 9, 53, 0)) - assert_equals(string_to_datetime('2011-04-09T09:53:00Z'), - dt_utz(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-05-22T00:24:15Z'), + dt_utz(2011, 5, 22, 0, 24, 15)) + eq_(string_to_datetime('2011-04-09T10:07:30Z'), + dt_utz(2011, 4, 9, 10, 7, 30)) + eq_(string_to_datetime('2011-02-19T07:16:11Z'), + dt_utz(2011, 2, 19, 7, 16, 11)) + eq_(string_to_datetime('2010-12-21T12:34:27Z'), + dt_utz(2010, 12, 21, 12, 34, 27)) + eq_(string_to_datetime('2011-04-09T10:20:05Z'), + dt_utz(2011, 4, 9, 10, 20, 5)) + eq_(string_to_datetime('2011-04-09T10:05:58Z'), + dt_utz(2011, 4, 9, 10, 5, 58)) + eq_(string_to_datetime('2011-04-09T09:53:00Z'), + dt_utz(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-04-09T10:00:21Z'), + dt_utz(2011, 4, 9, 10, 0, 21)) + eq_(string_to_datetime('2010-12-16T15:10:59Z'), + dt_utz(2010, 12, 16, 15, 10, 59)) + eq_(string_to_datetime('2011-04-09T09:53:00Z'), + dt_utz(2011, 4, 9, 9, 53, 0)) + eq_(string_to_datetime('2011-04-09T09:53:00Z'), + dt_utz(2011, 4, 9, 9, 53, 0)) def test_datetime_to_isodate(): - assert_equals(datetime_to_isodate(dt_utz(2011, 5, 22, 0, 24, 15)), - '2011-05-22T00:24:15Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 7, 30)), - '2011-04-09T10:07:30Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 2, 19, 7, 16, 11)), - '2011-02-19T07:16:11Z') - assert_equals(datetime_to_isodate(dt_utz(2010, 12, 21, 12, 34, 27)), - '2010-12-21T12:34:27Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 20, 5)), - '2011-04-09T10:20:05Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 5, 58)), - '2011-04-09T10:05:58Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 0, 21)), - '2011-04-09T10:00:21Z') - assert_equals(datetime_to_isodate(dt_utz(2010, 12, 16, 15, 10, 59)), - '2010-12-16T15:10:59Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00Z') - assert_equals(datetime_to_isodate(dt_utz(2011, 4, 9, 9, 53, 0)), - '2011-04-09T09:53:00Z') + eq_(datetime_to_isodate(dt_utz(2011, 5, 22, 0, 24, 15)), + '2011-05-22T00:24:15Z') + eq_(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 7, 30)), + '2011-04-09T10:07:30Z') + eq_(datetime_to_isodate(dt_utz(2011, 2, 19, 7, 16, 11)), + '2011-02-19T07:16:11Z') + eq_(datetime_to_isodate(dt_utz(2010, 12, 21, 12, 34, 27)), + '2010-12-21T12:34:27Z') + eq_(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 20, 5)), + '2011-04-09T10:20:05Z') + eq_(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 5, 58)), + '2011-04-09T10:05:58Z') + eq_(datetime_to_isodate(dt_utz(2011, 4, 9, 9, 53, 0)), + '2011-04-09T09:53:00Z') + eq_(datetime_to_isodate(dt_utz(2011, 4, 9, 10, 0, 21)), + '2011-04-09T10:00:21Z') + eq_(datetime_to_isodate(dt_utz(2010, 12, 16, 15, 10, 59)), + '2010-12-16T15:10:59Z') + eq_(datetime_to_isodate(dt_utz(2011, 4, 9, 9, 53, 0)), + '2011-04-09T09:53:00Z') + eq_(datetime_to_isodate(dt_utz(2011, 4, 9, 9, 53, 0)), + '2011-04-09T09:53:00Z') diff --git a/tests/test_unit.py b/tests/test_unit.py index 9d6650b..104256d 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -1,13 +1,18 @@ -# -*- coding: latin-1 -*- +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 Adam Vandenberg +# James Rowe +# Jeremy Dunck +# modocache +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. -import _setup - -import datetime import unittest -from nose.tools import (assert_equals, assert_true) +from mock import patch +from nose.tools import (eq_, ok_, raises) -from github2.core import repr_string +from github2.core import (AuthError, repr_string, requires_auth) from github2.issues import Issue from github2.client import Github @@ -15,59 +20,97 @@ class ReprTests(unittest.TestCase): + """__repr__ must return strings, not unicode objects.""" def test_issue(self): """Issues can have non-ASCII characters in the title.""" title = 'abcdé' i = Issue(title=title) - assert_equals(str, type(repr(i))) + eq_(str, type(repr(i))) + + +class HostSetting(unittest.TestCase): + def test_default_host(self): + client = Github() + eq_(client.request.github_url, 'https://github.com') + + def test_non_standard_host(self): + client = Github(github_url="http://your-github-enterprise-url.com/") + eq_(client.request.github_url, + 'http://your-github-enterprise-url.com/') class RateLimits(utils.HttpMockTestCase): - """Test API rate-limitting""" - def test_delays(self): - """Test call delay is at least one second""" + + """Test API rate-limiting.""" + + @patch('github2.request.time.sleep') + def test_delays(self, sleep): + """Test calls in quick succession are delayed.""" client = Github(requests_per_second=.5) client.users.show('defunkt') - start = datetime.datetime.now() client.users.show('mojombo') - end = datetime.datetime.now() - delta = end - start - delta_seconds = delta.days * 24 * 60 * 60 + delta.seconds - - assert_true(delta_seconds >= 2, - "Expected .5 reqs per second to require a 2 second delay " - "between calls.") + # 0.5 requests per second, means a two second delay + sleep.assert_called_once_with(2.0) class BaseDataIter(utils.HttpMockTestCase): - """Test iter availability of objects""" + + """Test iter availability of objects.""" + def test_iter(self): commit_id = '1c83cde9b5a7c396a01af1007fb7b88765b9ae45' commit = self.client.commits.show('ask/python-github2', commit_id) - assert_true('__iter__' in dir(commit)) + ok_('__iter__' in dir(commit)) class BaseDataDict(utils.HttpMockTestCase): - """Test __getitem__ availability on objects""" + + """Test dict compatibility on objects.""" + def test_getitem(self): user = self.client.users.show('defunkt') - assert_equals(user['blog'], user.blog) - assert_equals(user['company'], user.company) - assert_equals(user['email'], user.email) - assert_equals(user['location'], user.location) - assert_equals(user['login'], user.login) - assert_equals(user['name'], user.name) + eq_(user['blog'], user.blog) + eq_(user['company'], user.company) + eq_(user['email'], user.email) + eq_(user['location'], user.location) + eq_(user['login'], user.login) + eq_(user['name'], user.name) + + @raises(KeyError) + def test_getitem_failure(self): + user = self.client.users.show('defunkt') + ok_(user['invalid_key']) + + def test_setitem(self): + user = self.client.users.show('defunkt') + user['blog'] = 'http://example.com' + eq_(user['blog'], 'http://example.com') + + @raises(KeyError) + def test_setitem_failure(self): + user = self.client.users.show('defunkt') + user['invalid_key'] = 'test' def test_project_for_user_repo(): client = Github() - assert_equals(client.project_for_user_repo('JNRowe', 'misc-overlay'), + eq_(client.project_for_user_repo('JNRowe', 'misc-overlay'), 'JNRowe/misc-overlay') + def test_repr_string(): - assert_equals(repr_string('test'), 'test') - assert_equals(repr_string('abcdefghijklmnopqrst'), 'abcdefghijklmnopqrst') - assert_equals(repr_string('abcdefghijklmnopqrstu'), 'abcdefghijklmnopq...') + eq_(repr_string('test'), 'test') + eq_(repr_string('abcdefghijklmnopqrst'), 'abcdefghijklmnopqrst') + eq_(repr_string('abcdefghijklmnopqrstu'), 'abcdefghijklmnopq...') + + +class RequiresAuth(utils.HttpMockTestCase): + @raises(AuthError) + def test_no_auth(self): + f = lambda: None + f.__doc__ = 'test func' + wrapped = requires_auth(f) + wrapped(self.client) diff --git a/tests/test_user.py b/tests/test_user.py index 5fe4c5b..c2fab11 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,62 +1,66 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. import datetime -from nose.tools import (assert_equals, assert_false, assert_true) - -from github2.client import Github +from nose.tools import (eq_, assert_false, ok_) import utils class UserProperties(utils.HttpMockTestCase): - """Test user property handling""" + + """Test user property handling.""" + def test_user(self): user = self.client.users.show('defunkt') - assert_equals(user.blog, 'http://chriswanstrath.com/') - assert_equals(user.company, 'GitHub') - assert_equals(user.email, 'chris@wanstrath.com') - assert_equals(user.location, 'San Francisco') - assert_equals(user.login, 'defunkt') - assert_equals(user.name, 'Chris Wanstrath') + eq_(user.blog, 'http://chriswanstrath.com/') + eq_(user.company, 'GitHub') + eq_(user.email, 'chris@wanstrath.com') + eq_(user.location, 'San Francisco') + eq_(user.login, 'defunkt') + eq_(user.name, 'Chris Wanstrath') def test_meta(self): user = self.client.users.show('defunkt') - assert_equals(user.created_at, - datetime.datetime(2007, 10, 19, 22, 24, 19)) - assert_equals(user.followers_count, 3402) - assert_equals(user.following_count, 212) - assert_equals(user.gravatar_id, 'b8dbb1987e8e5318584865f880036796') - assert_equals(user.id, 2) - assert_equals(user.public_gist_count, 278) - assert_equals(user.public_repo_count, 93) + eq_(user.created_at, datetime.datetime(2007, 10, 19, 22, 24, 19)) + eq_(user.followers_count, 3402) + eq_(user.following_count, 212) + eq_(user.gravatar_id, 'b8dbb1987e8e5318584865f880036796') + eq_(user.id, 2) + eq_(user.public_gist_count, 278) + eq_(user.public_repo_count, 93) def test_followers(self): - assert_equals(len(self.client.users.followers('defunkt')), 3402) + eq_(len(self.client.users.followers('defunkt')), 3402) def test_following(self): - assert_equals(len(self.client.users.following('defunkt')), 212) + eq_(len(self.client.users.following('defunkt')), 212) def test_is_not_authenticated(self): user = self.client.users.show('defunkt') - assert_true(user.is_authenticated() is False) + ok_(user.is_authenticated() is False) class UserQueries(utils.HttpMockTestCase): - """Test user querying """ + + """Test user querying.""" + def test_search(self): - assert_equals(repr(self.client.users.search('James Rowe')), - '[, ]') + eq_(repr(self.client.users.search('James Rowe')), + '[, ]') def test_search_by_email(self): user = self.client.users.search_by_email('jnrowe@gmail.com') - assert_equals(repr(user), '') + eq_(repr(user), '') class AuthenticatedUserMethods(utils.HttpMockAuthenticatedTestCase): def test_follow(self): result = self.client.users.follow('defunkt') - assert_true('defunkt' in result['users']) + ok_('defunkt' in result['users']) def test_unfollow(self): result = self.client.users.unfollow('defunkt') @@ -64,21 +68,25 @@ def test_unfollow(self): def test_is_authenticated(self): user = self.client.users.show('') - assert_true(user.is_authenticated() is True) + ok_(user.is_authenticated() is True) + + def test_list_keys(self): + keys = self.client.users.list_keys() + eq_(keys[0].id, 1337) class AuthenticatedUserProperties(utils.HttpMockAuthenticatedTestCase): def test_private_data(self): user = self.client.users.show('') - assert_equals(user.total_private_repo_count, 0) - assert_equals(user.collaborators, 0) - assert_equals(user.disk_usage, 66069) - assert_equals(user.owned_private_repo_count, 0) - assert_equals(user.private_gist_count, 7) + eq_(user.total_private_repo_count, 0) + eq_(user.collaborators, 0) + eq_(user.disk_usage, 66069) + eq_(user.owned_private_repo_count, 0) + eq_(user.private_gist_count, 7) def test_plan_data(self): user = self.client.users.show('') - assert_equals(user.plan['name'], "free") - assert_equals(user.plan['collaborators'], 0) - assert_equals(user.plan['space'], 307200) - assert_equals(user.plan['private_repos'], 0) + eq_(user.plan['name'], "free") + eq_(user.plan['collaborators'], 0) + eq_(user.plan['space'], 307200) + eq_(user.plan['private_repos'], 0) diff --git a/tests/utils.py b/tests/utils.py index d05e258..313836e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,7 @@ -import _setup +# Copyright (C) 2011-2012 James Rowe +# +# This file is part of python-github2, and is made available under the 3-clause +# BSD license. See LICENSE for the full details. import os import sys @@ -8,6 +11,8 @@ import httplib2 +from mock import Mock + from github2.client import Github from github2.request import charset_from_headers @@ -16,84 +21,62 @@ bytes = lambda x, enc: x -HTTP_DATA_DIR = "tests/data/" +ORIG_REQUEST_METHOD = httplib2.Http.request -ORIG_HTTP_OBJECT = httplib2.Http +def request_mock(uri, method='GET', body=None, headers=None, + redirections=5, connection_type=None): + """Http mock side effect that returns saved entries. -class HttpMock(object): - """Simple Http mock that returns saved entries + Implementation tests should never span network boundaries. - Implementation tests should never span network boundaries """ - def __init__(self, cache=None, timeout=None, proxy_info=None, ca_certs=None): - """Create a mock httplib.Http object - - .. attribute: called_with - - ``locals()`` during ``__init__``, for testing call spec - """ - self.called_with = locals() - - def request(self, uri, method='GET', body=None, headers=None, - redirections=5, connection_type=None): - file = os.path.join(HTTP_DATA_DIR, httplib2.safename(uri)) - if os.path.exists(file): - response = message_from_file(open(file)) - headers = httplib2.Response(response) - body = bytes(response.get_payload(), charset_from_headers(headers)) - return (headers, body) - else: - return (httplib2.Response({"status": "404"}), - "Resource %r unavailable from test data store" % file) + file = os.path.join("tests/data", httplib2.safename(uri)) + if os.path.exists(file): + response = message_from_file(open(file)) + headers = httplib2.Response(response) + body = bytes(response.get_payload(), charset_from_headers(headers)) + return (headers, body) + else: + return (httplib2.Response({"status": "404"}), + "Resource %r unavailable from test data store" % file) class HttpMockTestCase(unittest.TestCase): def setUp(self): - """Prepare test fixtures + """Prepare test fixtures. `httplib2.Http` is patched to return cached entries via :class:`HttpMock`. :attr:`client` is an unauthenticated :obj:`Github` object for easy use in tests. + """ - httplib2.Http = HttpMock + httplib2.Http.request = Mock(spec_set=httplib2.Http.request, + side_effect=request_mock) self.client = Github() def tearDown(self): - """Remove test fixtures + """Remove test fixtures. `httplib2.Http` is returned to its original state. + """ - httplib2.Http = ORIG_HTTP_OBJECT + httplib2.Http.request = ORIG_REQUEST_METHOD class HttpMockAuthenticatedTestCase(HttpMockTestCase): def setUp(self): - """Prepare test fixtures + """Prepare test fixtures. - :see: ``HttpMockTestCase`` + :see: :class:`HttpMockTestCase` :attr:`client` is an authenticated :obj:`Github` object for easy use in tests. + """ - httplib2.Http = HttpMock + httplib2.Http.request = Mock(spec_set=httplib2.Http.request, + side_effect=request_mock) self.client = Github(access_token='xxx') - - -def set_http_mock(): - """Function to enable ``Http`` mock - - This is useful in simple `nose`-compliant test functions - """ - httplib2.Http = HttpMock - - -def unset_http_mock(): - """Function to disable ``Http`` mock - - :see: :func:`set_http_mock` - """ - httplib2.Http = ORIG_HTTP_OBJECT diff --git a/tox.ini b/tox.ini index 7a01449..ffb9775 100644 --- a/tox.ini +++ b/tox.ini @@ -6,10 +6,28 @@ envlist = py24, py25, py26, py27, py31, py32, rst, sphinx deps = nose coverage + mock commands = - rm -rf build - {envpython} setup.py build nosetests {posargs:-vv} tests +# When tox 1.4 is released use substitution to remove all the duplication. +[testenv:py24] +deps = + nose + coverage + mock + unittest2 +[testenv:py25] +deps = + nose + coverage + mock + unittest2 +[testenv:py26] +deps = + nose + coverage + mock + unittest2 [testenv:rst] deps = docutils