diff --git a/.gitignore b/.gitignore index 228a223..f0c73ea 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ *.rope* MANIFEST docs/_build +dist/ +build/ +*egg-info diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..44333a8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: python +python: + - "2.6" + - "2.7" +install: + - pip install -r requirements/dev.txt --use-mirrors +script: nosetests diff --git a/AUTHORS.rst b/AUTHORS.rst index af05118..ac8946c 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,4 +1,5 @@ python-github3 is written and maintained by **David Medina** and + various contributors: Development Lead @@ -11,8 +12,9 @@ Forked and redesign from Kenneth Reitz's repo Forked from https://github.com/kennethreitz/python-github3 -Kenneth Reitz is also the author of the awesome `requests `_ library -which `python-github3` needs it +Kenneth Reitz is also the author of the awesome +`requests `_ library, which +`python-github3` requires. Patches and Suggestions ......................... @@ -21,3 +23,11 @@ Patches and Suggestions - Rok Garbas - Antti Kaihola - Francisco Marcos +- Nathaniel Williams +- Alejandro Gómez +- Stefano Rivera +- Ouertani Mohammed Amine +- Conor Branagan +- Ralph Bean +- Jason A. Donenfeld +- Brad Montgomery diff --git a/MANIFEST.in b/MANIFEST.in index 93f4e76..072beda 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include AUTHORS.rst include README.rst include LICENSE -include requirements.txt +include requirements/base.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f2b3997 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +init: + pip install -r requirements/dev.txt diff --git a/README.rst b/README.rst index d37650b..dc80034 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,5 @@ +.. image:: https://secure.travis-ci.org/copitux/python-github3.png + Pygithub3 ========== @@ -41,13 +43,17 @@ Achievements - `Users service `_ - `Repos service `_ - `Gists service `_ +- `Git Data service `_ +- `Pull requests service `_ +- `Orgs service `_ +- `Issues service `_ +- `Events service `_ TODO ----- -- Services: Git Data, Issues, Orgs, Pull Requests, Events - Oauth authorization API (service?) -- Proxy methods into resources (e.g copitux.followers()) +- Proxy methods into resources (e.g copitux.followers) Contribute ----------- @@ -55,8 +61,10 @@ Contribute 1. Fork the `repository `_ 2. Write a test to cover new feature or to reproduce bug 3. Code with `pep8 `_ rules -4. Add you to ``AUTHORS`` -5. Pull request it +4. Add yourself to ``AUTHORS`` +5. Pull request it to ``develop`` branch + +Tests +----- -**Note**: I use `nose `_ test environment, -with `mock `_ ``pip install nose mock`` +Run ``make init`` to install test requirements and ``nosetests`` to run tests. diff --git a/docs/events.rst b/docs/events.rst new file mode 100644 index 0000000..49d0de5 --- /dev/null +++ b/docs/events.rst @@ -0,0 +1,150 @@ +.. _Events service: + +Events service +============== + +This service exposes the `Events API`_. Much of this API is read-only, and while +pagination is supported, there is a fixed page size of 30 with a limit of 10 +page requests. + +Many events have an `actor` which denotes the user that performed an event. +Additionally, there may be `org` or `repo` attributes for events related to +Organizations and Repos. Finally, each event object contains a `payload` +attribute containing more detailed information about the event. + +.. _public events: + +Public Events +------------- +Yields the most recent public events from Github. + +:: + + from pygithub3 import Github + + gh = Github() + + events = gh.events.list().all() + print events[0].payload + +Event +....... + +.. autoclass:: pygithub3.services.events.Event + :members: + + .. attribute:: issues + + :ref:`Issues events ` + + .. attribute:: networks + + :ref:`Events network service` + + .. attribute:: orgs + + :ref:`Events org service` + + .. attribute:: repos + + :ref:`Events repo service` + + .. attribute:: users + + :ref:`Events user service` + +.. _repository events: + +Repo Events +----------- + +These are events for a specific repo, including issue and network events. The +Issues events are proxied to the :ref:`Issues service`. + +:: + + events = gh.events.repos.list(user="copitux", repo="python-github3") + for e in events.next(): + print("{t}".format(t=e.type)) + + # Get the issue Events + events = gh.events.issues.list_by_repo(user="copitux", + repo="python-github3") + + # Get the Public Events for a Repo's Network + events = gh.events.networks.list(user="copitux", repo="python-github3") + +.. _Events network service: + +Network +....... + +.. autoclass:: pygithub3.services.events.networks.Network + :members: + + +.. _Events repo service: + +Repo +........ + +.. autoclass:: pygithub3.services.events.repos.Repo + :members: + + +.. _organziation events: + +Organization Events +------------------- + +These are the public events for an Organization + +:: + + events = gh.events.orgs.list(org="Acme") + +You may also get a user's feed of events for an Organization, but you *must* be +authenticated as the provided user, and you must be a member of the given +organization. + +:: + + events = gh.events.users.orgs(user="copitux", org="acme") + +.. _Events org service: + +Org +........ + +.. autoclass:: pygithub3.services.events.orgs.Org + :members: + +.. _user events: + +User Events +----------- + +You can retrieve the public events performed by a user and the public events +that a user receives. If you're authenticated, you may also receive private +events. + +:: + + received_events = gh.events.users.list_received_public(user="copitux") + performed_events = gh.events.users.list_performed_public(user="copitux") + +If authenticated as `copitux`, you could get private events with the +following, otherwise you'll just get the public events as above: + +:: + + received_events = gh.events.users.list_received(user="copitux") + performed_events = gh.events.users.list_performed(user="copitux") + +.. _Events user service: + +User +........ + +.. autoclass:: pygithub3.services.events.users.User + :members: diff --git a/docs/git_data.rst b/docs/git_data.rst new file mode 100644 index 0000000..9045841 --- /dev/null +++ b/docs/git_data.rst @@ -0,0 +1,91 @@ +.. _Git Data service: + +Git Data services +================= + +**Example**:: + + from pygithub3 import Github + + gh = Github(user='someone', repo='some_repo') + + a_blob = gh.git_data.blobs.get('a long sha') + + dev_branch = gh.git_data.references.get('heads/add_a_thing') + + +GitData +------- + +.. autoclass:: pygithub3.services.git_data.GitData + :members: + + .. attribute:: blobs + + :ref:`Blobs service` + + .. attribute:: commits + + :ref:`Gitdata commits service` + + .. attribute:: references + + :ref:`References service` + + .. attribute:: tags + + :ref:`Tags service` + + .. attribute:: trees + + :ref:`Trees service` + + +.. _Blobs service: + +Blobs +----- + +.. autoclass:: pygithub3.services.git_data.Blobs + :members: + + +.. _Gitdata commits service: + +Commits +------- + +.. autoclass:: pygithub3.services.git_data.Commits + :members: + + +.. _References service: + +References +---------- + +.. autoclass:: pygithub3.services.git_data.References + :members: + + +.. _Tags service: + +Tags +---- + +.. autoclass:: pygithub3.services.git_data.Tags + :members: + + +.. _Trees service: + +Trees +----- + +.. autoclass:: pygithub3.services.git_data.Trees + :members: + +.. _github commits doc: http://developer.github.com/v3/git/commits +.. _github refs doc: http://developer.github.com/v3/git/refs +.. _github tags doc: http://developer.github.com/v3/git/tags +.. _github trees doc: http://developer.github.com/v3/git/trees diff --git a/docs/installation.rst b/docs/installation.rst index 8ed6c10..0601a7e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -15,15 +15,21 @@ Dependencies Required ......... -This library only depends on `request library`_ module. +This library depends only on the `requests`_ module. -If you install ``pygithub3`` with ``pip`` all is done. The best option +If you install ``pygithub3`` with ``pip`` all is done. This is the best option. Optional ......... -If you want to run the test suite, you must install `nose`_ and `mock`_ +The test suite uses `nose`_, `mock`_, and `unittest2`_ (python 2.6). Compiling +the documentation requires `sphinx`_. -.. _request library: http://docs.python-requests.org/en/v0.10.6/index.html +Install all of these by running ``pip install -r test_requirements.txt``. Then +just run ``nosetests`` to run the tests. + +.. _requests: http://docs.python-requests.org/en/v0.10.6/index.html .. _nose: http://readthedocs.org/docs/nose/en/latest .. _mock: http://pypi.python.org/pypi/mock +.. _unittest2: http://pypi.python.org/pypi/unittest2 +.. _sphinx: http://sphinx.pocoo.org/ diff --git a/docs/issues.rst b/docs/issues.rst new file mode 100644 index 0000000..3c35eb8 --- /dev/null +++ b/docs/issues.rst @@ -0,0 +1,74 @@ +.. _Issues service: + +Issues services +=============== + +**Fast sample**:: + + from pygithub3 import Github + + auth = dict(login='octocat', password='pass') + gh = Github(**auth) + + octocat_issues = gh.issues.list() + octocat_repo_issues = gh.issues.list_by_repo('octocat', 'Hello-World') + +Issues +------ + +.. autoclass:: pygithub3.services.issues.Issue + :members: + + .. attribute:: comments + + :ref:`Issues comments service` + + .. attribute:: events + + :ref:`Issues events service` + + .. attribute:: labels + + :ref:`Labels service` + + .. attribute:: milestones + + :ref:`Milestones service` + +.. _Issues comments service: + +Comments +-------- + +.. autoclass:: pygithub3.services.issues.Comments + :members: + +.. _Issues events service: + +Events +------ + +.. autoclass:: pygithub3.services.issues.Events + :members: + +.. _Labels service: + +Labels +------ + +.. autoclass:: pygithub3.services.issues.Labels + :members: + +.. _Milestones service: + +Milestones +---------- + +.. autoclass:: pygithub3.services.issues.Milestones + :members: + +.. _github issues doc: http://developer.github.com/v3/issues +.. _github comments doc: http://developer.github.com/v3/issues/comments +.. _github events doc: http://developer.github.com/v3/issues/events +.. _github labels doc: http://developer.github.com/v3/issues/labels +.. _github milestones doc: http://developer.github.com/v3/issues/milestones diff --git a/docs/orgs.rst b/docs/orgs.rst new file mode 100644 index 0000000..96e7a73 --- /dev/null +++ b/docs/orgs.rst @@ -0,0 +1,46 @@ +.. _Orgs service: + +Orgs services +============== + +**Fast sample**:: + + from pygithub3 import Github + + gh = Github(token='abc123') + + auth_orgs = gh.orgs.list().all() + members = gh.orgs.members.list('github') + +Org +------ + +.. autoclass:: pygithub3.services.orgs.Org + :members: + + .. attribute:: members + + :ref:`Members service` + + .. attribute:: teams + + :ref:`Teams service` + +.. _Members service: + +Members +--------- + +.. autoclass:: pygithub3.services.orgs.members.Members + :members: + +.. _Teams service: + +Teams +------- + +.. autoclass:: pygithub3.services.orgs.teams.Teams + :members: + +.. _github orgs doc: http://developer.github.com/v3/orgs +.. _github orgs teams doc: http://developer.github.com/v3/orgs/teams diff --git a/docs/pull_requests.rst b/docs/pull_requests.rst new file mode 100644 index 0000000..09313eb --- /dev/null +++ b/docs/pull_requests.rst @@ -0,0 +1,35 @@ +.. _Pull Requests service: + +Pull Requests service +===================== + +**Example**:: + + from pygithub3 import Github + + gh = Github(user='octocat', repo='sample') + + pull_requests = gh.pull_requests.list().all() + pull_request_commits = gh.pull_requests.list_commits(2512).all() + +Pull Requests +------------- + +.. autoclass:: pygithub3.services.pull_requests.PullRequests + :members: + + .. attribute:: comments + + :ref:`Pull Request Comments service` + + +.. _Pull Request Comments service: + +Pull Request Comments +--------------------- + +.. autoclass:: pygithub3.services.pull_requests.Comments + :members: + +.. _github pullrequests doc: http://developer.github.com/v3/pulls +.. _github pullrequests comments doc: http://developer.github.com/v3/pulls/comments diff --git a/docs/services.rst b/docs/services.rst index 71fa690..998fb7e 100644 --- a/docs/services.rst +++ b/docs/services.rst @@ -72,5 +72,10 @@ List of services users repos gists + git_data + pull_requests + orgs + issues + events .. _mimetypes: http://developer.github.com/v3/mime diff --git a/pygithub3/__init__.py b/pygithub3/__init__.py index 9a626f3..deff002 100644 --- a/pygithub3/__init__.py +++ b/pygithub3/__init__.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python # -*- encoding: utf-8 -*- __title__ = 'pygithub3' -__version__ = '0.3' +__version__ = '0.5.1' __author__ = 'David Medina' __email__ = 'davidmedina9@gmail.com' __license__ = 'ISC' diff --git a/pygithub3/core/client.py b/pygithub3/core/client.py index ee7c97f..4d719e5 100644 --- a/pygithub3/core/client.py +++ b/pygithub3/core/client.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- encoding: utf-8 -*- import requests @@ -56,6 +55,8 @@ def __set_params(self, config): self.requester.params['per_page'] = config.get('per_page') if config.get('verbose'): self.requester.config = {'verbose': config['verbose']} + if config.get('timeout'): + self.requester.timeout = config['timeout'] def __parse_kwargs(func): """ Decorator to put extra args into requests.params """ diff --git a/pygithub3/core/utils.py b/pygithub3/core/compat.py similarity index 100% rename from pygithub3/core/utils.py rename to pygithub3/core/compat.py diff --git a/pygithub3/core/errors.py b/pygithub3/core/errors.py index 4a95df0..d86b2dd 100644 --- a/pygithub3/core/errors.py +++ b/pygithub3/core/errors.py @@ -1,11 +1,7 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -try: - import simplejson as json -except ImportError: - import json - +from pygithub3.core import json from pygithub3.exceptions import NotFound, BadRequest, UnprocessableEntity diff --git a/pygithub3/core/json/__init__.py b/pygithub3/core/json/__init__.py new file mode 100644 index 0000000..f0adb20 --- /dev/null +++ b/pygithub3/core/json/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +""" +Emulate json module with encode/decoders to support github datetime format +""" + +from datetime import datetime +try: + import simplejson as json +except ImportError: + import json + +GITHUB_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ' + + +class GHJSONEncoder(json.JSONEncoder): + def default(self, o): + try: + return datetime.strftime(o, GITHUB_DATE_FORMAT) + except: + return super(GHJSONEncoder, self).default(o) + + +def gh_decoder_hook(dict_): + for k, v in dict_.iteritems(): + try: + date = datetime.strptime(v, GITHUB_DATE_FORMAT) + dict_[k] = date + except: + continue + return dict_ + + +def dumps(obj, cls=GHJSONEncoder, **kwargs): + return json.dumps(obj, cls=cls, **kwargs) + + +def loads(s, object_hook=gh_decoder_hook, **kwargs): + return json.loads(s, object_hook=object_hook, **kwargs) + +dump = json.dump +load = json.load diff --git a/pygithub3/exceptions.py b/pygithub3/exceptions.py index a467256..9ee2597 100644 --- a/pygithub3/exceptions.py +++ b/pygithub3/exceptions.py @@ -8,7 +8,7 @@ class InvalidBodySchema(Exception): pass -class DoesNotExists(Exception): +class RequestDoesNotExist(Exception): """ Raised when `Request` factory can't find the subclass """ pass @@ -37,6 +37,6 @@ class UnprocessableEntity(Exception): class NotFound(Exception): """ Raised when server response is 404 - Catched with a pygithub3-exception to `services.base.Service._bool` method + Caught with a pygithub3-exception to `services.base.Service._bool` method """ pass diff --git a/pygithub3/github.py b/pygithub3/github.py index 0b302a1..d64577c 100644 --- a/pygithub3/github.py +++ b/pygithub3/github.py @@ -17,9 +17,19 @@ def __init__(self, **config): from pygithub3.services.users import User from pygithub3.services.repos import Repo from pygithub3.services.gists import Gist + from pygithub3.services.events import Event + from pygithub3.services.git_data import GitData + from pygithub3.services.pull_requests import PullRequests + from pygithub3.services.orgs import Org + from pygithub3.services.issues import Issue self._users = User(**config) self._repos = Repo(**config) self._gists = Gist(**config) + self._git_data = GitData(**config) + self._pull_requests = PullRequests(**config) + self._orgs = Org(**config) + self._issues = Issue(**config) + self._events = Event(**config) @property def remaining_requests(self): @@ -47,3 +57,38 @@ def gists(self): :ref:`Gists service ` """ return self._gists + + @property + def git_data(self): + """ + :ref:`Git Data service ` + """ + return self._git_data + + @property + def pull_requests(self): + """ + :ref:`Pull Requests service ` + """ + return self._pull_requests + + @property + def orgs(self): + """ + :ref:`Orgs service ` + """ + return self._orgs + + @property + def issues(self): + """ + :ref:`Issues service ` + """ + return self._issues + + @property + def events(self): + """ + :ref:`Events service ` + """ + return self._events diff --git a/pygithub3/requests/base.py b/pygithub3/requests/base.py index 03b0f8a..7eb0ff3 100644 --- a/pygithub3/requests/base.py +++ b/pygithub3/requests/base.py @@ -1,21 +1,18 @@ -#!/usr/bin/env python # -*- encoding: utf-8 -*- import re -try: - import simplejson as json -except ImportError: - import json -from pygithub3.exceptions import (DoesNotExists, UriInvalid, ValidationError, - InvalidBodySchema) +from pygithub3.core import json +from pygithub3.core.compat import import_module +from pygithub3.exceptions import (RequestDoesNotExist, UriInvalid, + ValidationError, InvalidBodySchema) from pygithub3.resources.base import Raw -from pygithub3.core.utils import import_module ABS_IMPORT_PREFIX = 'pygithub3.requests' class Body(object): + """ Input's request handler """ def __init__(self, content, schema, required): self.content = content @@ -28,9 +25,10 @@ def dumps(self): return json.dumps(self.parse()) def parse(self): + """ Parse body with schema-required rules """ if not hasattr(self.content, 'items'): - raise ValidationError("'%s' needs a content dictionary" - % self.__class__.__name__) + raise ValidationError("It needs a content dictionary (%s)" % ( + self.content, )) parsed = dict([(key, self.content[key]) for key in self.schema if key in self.content]) for attr_required in self.required: @@ -44,21 +42,34 @@ def parse(self): class Request(object): - """ """ uri = '' resource = Raw body_schema = {} def __init__(self, **kwargs): - """ """ - self.body = kwargs.pop('body', None) + self.body = kwargs.pop('body', {}) self.args = kwargs self.clean() + def __getattr__(self, name): + return self.args.get(name) + + def __str__(self): + return self.populate_uri() + + def populate_uri(self): + try: + populated_uri = self.uri.format(**self.args) + except KeyError: + raise ValidationError( + "'%s' request wasn't be able to populate the uri '%s' with " + "'%s' args" % (self.__class__.__name__, self.uri, self.args)) + return str(populated_uri).strip('/') + def clean(self): self.uri = self.clean_uri() or self.uri - self.body = Body(self.clean_body(), **self.clean_valid_body()) + self.body = Body(self.clean_body(), **self._clean_valid_body()) def clean_body(self): return self.body @@ -66,7 +77,7 @@ def clean_body(self): def clean_uri(self): return None - def clean_valid_body(self): + def _clean_valid_body(self): schema = set(self.body_schema.get('schema', ())) required = set(self.body_schema.get('required', ())) if not required.issubset(schema): @@ -76,21 +87,6 @@ def clean_valid_body(self): self.__class__.__name__, required, schema)) return dict(schema=schema, required=required) - def __getattr__(self, name): - return self.args.get(name) - - def __str__(self): - return self.populate_uri() - - def populate_uri(self): - try: - populated_uri = self.uri.format(**self.args) - except KeyError: - raise ValidationError( - "'%s' request wasn't be able to populate the uri '%s' with " - "'%s' args" % (self.__class__.__name__, self.uri, self.args)) - return str(populated_uri).strip('/') - def get_body(self): return self.body.dumps() @@ -110,28 +106,21 @@ def wrapper(self, request_uri, **kwargs): return func(self, request_uri.lower(), **kwargs) return wrapper - def dispatch(func): - def wrapper(self, request_uri, **kwargs): - module_chunk, s, request_chunk = request_uri.rpartition('.') - request_chunk = request_chunk.capitalize() - try: - # TODO: CamelCase and under_score support, now only Class Name - module = import_module('%s.%s' - % (ABS_IMPORT_PREFIX, module_chunk)) - request = getattr(module, request_chunk) - except ImportError: - raise DoesNotExists("'%s' module does not exists" - % module_chunk) - except AttributeError: - raise DoesNotExists( - "'%s' request doesn't exists into '%s' module" - % (request_chunk, module_chunk)) - return func(self, request, **kwargs) - return wrapper - @validate - @dispatch - def __call__(self, request='', **kwargs): - request = request(**kwargs) - assert isinstance(request, Request) - return request + def __call__(self, request_uri, **kwargs): + module_chunk, s, request_chunk = request_uri.rpartition('.') + request_chunk = request_chunk.capitalize() + try: + # TODO: CamelCase and under_score support, now only Class Name + module = import_module('%s.%s' % (ABS_IMPORT_PREFIX, module_chunk)) + request_class = getattr(module, request_chunk) + request = request_class(**kwargs) + assert isinstance(request, Request) + return request + except ImportError: + raise RequestDoesNotExist("'%s' module does not exist" + % module_chunk) + except AttributeError: + raise RequestDoesNotExist("'%s' request does not exist in " + "'%s' module" % (request_chunk, + module_chunk)) diff --git a/pygithub3/requests/events/__init__.py b/pygithub3/requests/events/__init__.py new file mode 100644 index 0000000..fd4988f --- /dev/null +++ b/pygithub3/requests/events/__init__.py @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.events import Event + + +class List(Request): + + uri = 'events' + resource = Event diff --git a/pygithub3/requests/events/networks.py b/pygithub3/requests/events/networks.py new file mode 100644 index 0000000..e510773 --- /dev/null +++ b/pygithub3/requests/events/networks.py @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- + +from . import Request +from pygithub3.resources.events import Network + + +class List(Request): + + uri = 'networks/{user}/{repo}/events' + resource = Network diff --git a/pygithub3/requests/events/orgs.py b/pygithub3/requests/events/orgs.py new file mode 100644 index 0000000..c8e9fc7 --- /dev/null +++ b/pygithub3/requests/events/orgs.py @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- + +from . import Request +from pygithub3.resources.events import Org + + +class List(Request): + + uri = 'orgs/{org}/events' + resource = Org diff --git a/pygithub3/requests/events/repos.py b/pygithub3/requests/events/repos.py new file mode 100644 index 0000000..6bb2f98 --- /dev/null +++ b/pygithub3/requests/events/repos.py @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- + +from . import Request +from pygithub3.resources.events import Repo + + +class List(Request): + + uri = 'repos/{user}/{repo}/events' + resource = Repo diff --git a/pygithub3/requests/events/users.py b/pygithub3/requests/events/users.py new file mode 100644 index 0000000..a8556ae --- /dev/null +++ b/pygithub3/requests/events/users.py @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- + +from . import Request +from pygithub3.resources.events import User, Org + + +class List_received(Request): + + uri = 'users/{user}/received_events' + resource = User + + +class List_received_public(Request): + + uri = 'users/{user}/received_events/public' + resource = User + + +class List_performed(Request): + + uri = 'users/{user}/events' + resource = User + + +class List_performed_public(Request): + + uri = 'users/{user}/events/public' + resource = User + + +class List_org_events(Request): + + uri = 'users/{user}/events/orgs/{org}' + resource = Org diff --git a/pygithub3/requests/gists/__init__.py b/pygithub3/requests/gists/__init__.py index 4dbc6e6..a8d92b7 100644 --- a/pygithub3/requests/gists/__init__.py +++ b/pygithub3/requests/gists/__init__.py @@ -1,8 +1,9 @@ # -*- encoding: utf-8 -*- -from pygithub3.requests.base import Request, ValidationError +from pygithub3.requests.base import Request from pygithub3.resources.gists import Gist + class List(Request): uri = 'users/{user}/gists' diff --git a/pygithub3/requests/gists/comments.py b/pygithub3/requests/gists/comments.py index 8e5af15..1da78cb 100644 --- a/pygithub3/requests/gists/comments.py +++ b/pygithub3/requests/gists/comments.py @@ -3,6 +3,7 @@ from pygithub3.requests.base import Request from pygithub3.resources.gists import Comment + class List(Request): uri = 'gists/{gist_id}/comments' diff --git a/pygithub3/requests/git_data/__init__.py b/pygithub3/requests/git_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pygithub3/requests/git_data/blobs.py b/pygithub3/requests/git_data/blobs.py new file mode 100644 index 0000000..a4bddd6 --- /dev/null +++ b/pygithub3/requests/git_data/blobs.py @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Blob + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/blobs/{sha}' + resource = Blob + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/blobs' + resource = Blob + body_schema = { + 'schema': ('content', 'encoding'), + 'required': ('content', 'encoding'), # TODO: is enc really required? + } diff --git a/pygithub3/requests/git_data/commits.py b/pygithub3/requests/git_data/commits.py new file mode 100644 index 0000000..a26e07d --- /dev/null +++ b/pygithub3/requests/git_data/commits.py @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.repos import Commit + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/commits/{sha}' + resource = Commit + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/commits' + resource = Commit + body_schema = { + 'schema': ('message', 'tree', 'parents', 'author', 'committer'), + 'required': ('message', 'tree', 'parents'), + } diff --git a/pygithub3/requests/git_data/references.py b/pygithub3/requests/git_data/references.py new file mode 100644 index 0000000..9824bdb --- /dev/null +++ b/pygithub3/requests/git_data/references.py @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Reference + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/refs/{ref}' + resource = Reference + + +class List(Request): + uri = 'repos/{user}/{repo}/git/refs/{namespace}' + resource = Reference + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/refs' + resource = Reference + body_schema = { + 'schema': ('ref', 'sha'), + 'required': ('ref', 'sha'), + } + + +class Update(Request): + uri = 'repos/{user}/{repo}/git/refs/{ref}' + resource = Reference + body_schema = { + 'schema': ('sha', 'force'), + 'required': ('sha',), + } + + +class Delete(Request): + uri = 'repos/{user}/{repo}/git/refs/{ref}' diff --git a/pygithub3/requests/git_data/tags.py b/pygithub3/requests/git_data/tags.py new file mode 100644 index 0000000..88a79c0 --- /dev/null +++ b/pygithub3/requests/git_data/tags.py @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Tag + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/tags/{sha}' + resource = Tag + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/tags' + resource = Tag + body_schema = { + 'schema': ('tag', 'message', 'object', 'type', 'tagger'), + 'required': ('type',), + } diff --git a/pygithub3/requests/git_data/trees.py b/pygithub3/requests/git_data/trees.py new file mode 100644 index 0000000..11d6409 --- /dev/null +++ b/pygithub3/requests/git_data/trees.py @@ -0,0 +1,16 @@ +from pygithub3.requests.base import Request +from pygithub3.resources.git_data import Tree + + +class Get(Request): + uri = 'repos/{user}/{repo}/git/trees/{sha}' + resource = Tree + + +class Create(Request): + uri = 'repos/{user}/{repo}/git/trees' + resource = Tree + body_schema = { + 'schema': ('tree', 'base_tree'), + 'required': ('tree',), + } diff --git a/pygithub3/requests/issues/__init__.py b/pygithub3/requests/issues/__init__.py new file mode 100644 index 0000000..d6c7b06 --- /dev/null +++ b/pygithub3/requests/issues/__init__.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.issues import Issue + + +class List(Request): + + uri = 'issues' + resource = Issue + + +class List_by_repo(Request): + + uri = 'repos/{user}/{repo}/issues' + resource = Issue + + +class Get(Request): + + uri = 'repos/{user}/{repo}/issues/{number}' + resource = Issue + + +class Create(Request): + + uri = 'repos/{user}/{repo}/issues' + resource = Issue + body_schema = { + 'schema': ('title', 'body', 'assignee', 'milestone', 'labels'), + 'required': ('title', ) + } + + +class Update(Request): + + uri = 'repos/{user}/{repo}/issues/{number}' + resource = Issue + body_schema = { + 'schema': ('title', 'body', 'assignee', 'state', 'milestone', + 'lables'), + 'required': () + } diff --git a/pygithub3/requests/issues/comments.py b/pygithub3/requests/issues/comments.py new file mode 100644 index 0000000..c9618a8 --- /dev/null +++ b/pygithub3/requests/issues/comments.py @@ -0,0 +1,42 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.issues import Comment + + +class List(Request): + + uri = 'repos/{user}/{repo}/issues/{number}/comments' + resource = Comment + + +class Get(Request): + + uri = 'repos/{user}/{repo}/issues/comments/{id}' + resource = Comment + + +class Create(Request): + + uri = 'repos/{user}/{repo}/issues/{number}/comments' + resource = Comment + body_schema = { + 'schema': ('body', ), + 'required': ('body', ) + } + + +class Edit(Request): + + uri = 'repos/{user}/{repo}/issues/comments/{id}' + resource = Comment + body_schema = { + 'schema': ('body', ), + 'required': ('body', ) + } + + +class Delete(Request): + + uri = 'repos/{user}/{repo}/issues/comments/{id}' + resource = Comment diff --git a/pygithub3/requests/issues/events.py b/pygithub3/requests/issues/events.py new file mode 100644 index 0000000..d284681 --- /dev/null +++ b/pygithub3/requests/issues/events.py @@ -0,0 +1,22 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.issues import Event + + +class List_by_issue(Request): + + uri = 'repos/{user}/{repo}/issues/{number}/events' + resource = Event + + +class List_by_repo(Request): + + uri = 'repos/{user}/{repo}/issues/events' + resource = Event + + +class Get(Request): + + uri = 'repos/{user}/{repo}/issues/events/{id}' + resource = Event diff --git a/pygithub3/requests/issues/labels.py b/pygithub3/requests/issues/labels.py new file mode 100644 index 0000000..7ba6c7a --- /dev/null +++ b/pygithub3/requests/issues/labels.py @@ -0,0 +1,77 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.issues import Label + + +class List(Request): + + uri = 'repos/{user}/{repo}/labels' + resource = Label + + +class Get(Request): + uri = 'repos/{user}/{repo}/labels/{name}' + resource = Label + + +class Create(Request): + uri = 'repos/{user}/{repo}/labels' + resource = Label + body_schema = { + 'schema': ('name', 'color'), + 'required': ('name', 'color') + } + + def clean_body(self): + color = self.body.get('color', '') + if not Label.is_valid_color(color): + raise ValidationError('colors must have 6 hexadecimal characters, ' + 'without # in the beggining') + else: + return self.body + + +class Update(Create): + + uri = 'repos/{user}/{repo}/labels/{name}' + resource = Label + body_schema = { + 'schema': ('name', 'color'), + 'required': ('name', 'color') + } + + +class Delete(Request): + uri = 'repos/{user}/{repo}/labels/{name}' + resource = Label + + +class List_by_issue(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + + +class Add_to_issue(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + + +class Remove_from_issue(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels/{name}' + resource = Label + + +class Replace_all(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + + +class Remove_all(Request): + uri = 'repos/{user}/{repo}/issues/{number}/labels' + resource = Label + + +class List_by_milestone(Request): + uri = 'repos/{user}/{repo}/milestones/{number}/labels' + resource = Label diff --git a/pygithub3/requests/issues/milestones.py b/pygithub3/requests/issues/milestones.py new file mode 100644 index 0000000..4093c7e --- /dev/null +++ b/pygithub3/requests/issues/milestones.py @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.issues import Milestone + + +class List(Request): + uri = 'repos/{user}/{repo}/milestones' + resource = Milestone + + +class Get(Request): + uri = 'repos/{user}/{repo}/milestones/{number}' + resource = Milestone + + +class Create(Request): + uri = 'repos/{user}/{repo}/milestones' + resource = Milestone + body_schema = { + 'schema': ('title', 'state', 'description', 'due_on'), + 'required': ('title',) + } + + def clean_body(self): # Test if API normalize it + state = self.body.get('state', '') + if state and state.lower() not in ('open', 'closed'): + raise ValidationError("'state' must be 'open' or 'closed'") + return self.body + + +class Update(Create): + + uri = 'repos/{user}/{repo}/milestones/{number}' + + +class Delete(Request): + uri = 'repos/{user}/{repo}/milestones/{number}' + resource = Milestone diff --git a/pygithub3/requests/orgs/__init__.py b/pygithub3/requests/orgs/__init__.py new file mode 100644 index 0000000..deba5ef --- /dev/null +++ b/pygithub3/requests/orgs/__init__.py @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request +from pygithub3.resources.orgs import Org + + +class List(Request): + uri = 'users/{user}/orgs' + resource = Org + + def clean_uri(self): + if not self.user: + return 'user/orgs' + + +class Get(Request): + uri = 'orgs/{org}' + resource = Org + + +class Update(Request): + uri = 'orgs/{org}' + resource = Org + body_schema = { + 'schema': ('billing_email', 'company', 'email', 'location', 'name'), + 'required': (), + } diff --git a/pygithub3/requests/orgs/members.py b/pygithub3/requests/orgs/members.py new file mode 100644 index 0000000..a6b05c2 --- /dev/null +++ b/pygithub3/requests/orgs/members.py @@ -0,0 +1,34 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.resources.orgs import Member +from . import Request + + +class List(Request): + uri = 'orgs/{org}/members' + resource = Member + + +class Is_member(Request): + uri = 'orgs/{org}/members/{user}' + + +class Delete(Request): + uri = 'orgs/{org}/members/{user}' + + +class Listpublic(Request): + uri = 'orgs/{org}/public_members' + resource = Member + + +class Is_public_member(Request): + uri = 'orgs/{org}/public_members/{user}' + + +class Publicize(Request): + uri = 'orgs/{org}/public_members/{user}' + + +class Conceal(Request): + uri = 'orgs/{org}/public_members/{user}' diff --git a/pygithub3/requests/orgs/teams.py b/pygithub3/requests/orgs/teams.py new file mode 100644 index 0000000..cc92f9c --- /dev/null +++ b/pygithub3/requests/orgs/teams.py @@ -0,0 +1,74 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.resources.orgs import Member, Team +from pygithub3.resources.repos import Repo +from . import Request + + +class List(Request): + uri = 'orgs/{org}/teams' + resource = Team + + +class Get(Request): + uri = 'teams/{id}' + resource = Team + + +class Create(Request): + uri = 'orgs/{org}/teams' + resource = Team + body_schema = { + 'schema': ('name', 'repo_names', 'permission',), + 'required': ('name',), + } + + # TODO: Check if this request fails with invalid permission + #def clean_body(self): + + +class Update(Request): + uri = 'teams/{id}' + resource = Team + body_schema = { + 'schema': ('name', 'permission',), + 'required': ('name',), + } + + +class Delete(Request): + uri = 'teams/{id}' + + +class List_members(Request): + uri = 'teams/{id}/members' + resource = Member + + +class Is_member(Request): + uri = 'teams/{id}/members/{user}' + + +class Add_member(Request): + uri = 'teams/{id}/members/{user}' + + +class Remove_member(Request): + uri = 'teams/{id}/members/{user}' + + +class List_repos(Request): + uri = 'teams/{id}/repos' + resource = Repo + + +class Contains_repo(Request): + uri = 'teams/{id}/repos/{user}/{repo}' + + +class Add_repo(Request): + uri = 'teams/{id}/repos/{user}/{repo}' + + +class Remove_repo(Request): + uri = 'teams/{id}/repos/{user}/{repo}' diff --git a/pygithub3/requests/pull_requests/__init__.py b/pygithub3/requests/pull_requests/__init__.py new file mode 100644 index 0000000..5845dd7 --- /dev/null +++ b/pygithub3/requests/pull_requests/__init__.py @@ -0,0 +1,69 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.pull_requests import PullRequest, File +from pygithub3.resources.repos import Commit + + +class List(Request): + uri = 'repos/{user}/{repo}/pulls' + resource = PullRequest + + +class Get(Request): + uri = 'repos/{user}/{repo}/pulls/{number}' + resource = PullRequest + + +class Create(Request): + uri = 'repos/{user}/{repo}/pulls' + resource = PullRequest + body_schema = { + 'schema': ('title', 'body', 'base', 'head', 'issue'), + 'required': ('base', 'head'), + } + + def clean_body(self): + if (not ('title' in self.body and 'body' in self.body) and + not 'issue' in self.body): + raise ValidationError('pull request creation requires either an ' + 'issue number or a title and body') + return self.body + + +class Update(Request): + uri = 'repos/{user}/{repo}/pulls/{number}' + resource = PullRequest + body_schema = { + 'schema': ('title', 'body', 'state'), + 'required': (), + } + + def clean_body(self): + if ('state' in self.body and + self.body['state'] not in ['open', 'closed']): + raise ValidationError('If a state is specified, it must be one ' + 'of "open" or "closed"') + return self.body + + +class List_commits(Request): + uri = 'repos/{user}/{repo}/pulls/{number}/commits' + resource = Commit + + +class List_files(Request): + uri = 'repos/{user}/{repo}/pulls/{number}/files' + resource = File + + +class Is_merged(Request): + uri = 'repos/{user}/{repo}/pulls/{number}/merge' + + +class Merge(Request): + uri = 'repos/{user}/{repo}/pulls/{number}/merge' + body_schema = { + 'schema': ('commit_message',), + 'required': (), + } diff --git a/pygithub3/requests/pull_requests/comments.py b/pygithub3/requests/pull_requests/comments.py new file mode 100644 index 0000000..1904f6c --- /dev/null +++ b/pygithub3/requests/pull_requests/comments.py @@ -0,0 +1,46 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.requests.base import Request, ValidationError +from pygithub3.resources.pull_requests import Comment + + +class List(Request): + uri = 'repos/{user}/{repo}/pulls/{number}/comments' + resource = Comment + + +class Get(Request): + uri = 'repos/{user}/{repo}/pulls/comments/{number}' + resource = Comment + + +class Create(Request): + uri = 'repos/{user}/{repo}/pulls/{number}/comments' + resource = Comment + body_schema = { + 'schema': ('body', 'commit_id', 'path', 'position', 'in_reply_to'), + 'required': ('body',), + } + + def clean_body(self): + if (not ('commit_id' in self.body and + 'path' in self.body and + 'position' in self.body) and + not 'in_reply_to' in self.body): + raise ValidationError('supply either in_reply_to or commit_id, ' + 'path, and position') + return self.body + + +class Update(Request): + uri = 'repos/{user}/{repo}/pulls/comments/{number}' + resource = Comment + body_schema = { + 'schema': ('body',), + 'required': ('body',), + } + + +class Delete(Request): + uri = 'repos/{user}/{repo}/pulls/comments/{number}' + resource = Comment diff --git a/pygithub3/requests/repos/__init__.py b/pygithub3/requests/repos/__init__.py index cd920fe..0474ec0 100644 --- a/pygithub3/requests/repos/__init__.py +++ b/pygithub3/requests/repos/__init__.py @@ -1,8 +1,10 @@ # -*- encoding: utf-8 -*- -from pygithub3.requests.base import Request, ValidationError +from pygithub3.requests.base import Request +from pygithub3.resources.orgs import Team +from pygithub3.resources.repos import Repo, Tag, Branch from pygithub3.resources.users import User -from pygithub3.resources.repos import Repo, Team, Tag, Branch + class List(Request): @@ -41,6 +43,12 @@ class Get(Request): resource = Repo +class Delete(Request): + + uri = 'repos/{user}/{repo}' + resource = Repo + + class Update(Request): uri = 'repos/{user}/{repo}' @@ -74,6 +82,7 @@ class List_tags(Request): uri = 'repos/{user}/{repo}/tags' resource = Tag + class List_branches(Request): uri = 'repos/{user}/{repo}/branches' diff --git a/pygithub3/resources/base.py b/pygithub3/resources/base.py index 4ca2aa3..25b045c 100644 --- a/pygithub3/resources/base.py +++ b/pygithub3/resources/base.py @@ -1,10 +1,6 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- -try: - import simplejson as json -except ImportError: - import json +from pygithub3.core import json class Resource(object): @@ -14,7 +10,6 @@ class Resource(object): _collection_maps = {} def __init__(self, attrs): - """ """ self._attrs = attrs self.__set_attrs() @@ -46,14 +41,6 @@ def wrapper(resource, raw_resource): return func(resource, raw_resource) return wrapper - def parse_date(string_date): - from datetime import datetime - try: - date = datetime.strptime(string_date, '%Y-%m-%dT%H:%M:%SZ') - except TypeError: - date = None - return date - @self_resource def parse_map(resource, raw_resource): if hasattr(raw_resource, 'items'): @@ -73,9 +60,6 @@ def parse_collection_map(resource, raw_resources): for raw_resource in raw_resources] new_resource = raw_resource.copy() - new_resource.update(dict([ - (attr, parse_date(raw_resource[attr])) - for attr in self._dates if attr in raw_resource])) new_resource.update(dict([ (attr, parse_map(resource, raw_resource[attr])) for attr, resource in self._maps.items() diff --git a/pygithub3/resources/events.py b/pygithub3/resources/events.py new file mode 100644 index 0000000..d779c23 --- /dev/null +++ b/pygithub3/resources/events.py @@ -0,0 +1,49 @@ +# -*- encoding: utf-8 -*- + +from pygithub3.resources.base import Resource +from pygithub3.resources import users, repos, orgs + + +class Event(Resource): + + _dates = ('created_at', ) + _maps = {'actor': users.User, 'repo': repos.Repo, 'org': orgs.Org} + + def __str__(self): + return '<(%s)>' % getattr(self, 'type', '') + + +class Repo(Resource): + + _dates = ('created_at', ) + _maps = {'actor': users.User, 'repo': repos.Repo, 'org': orgs.Org} + + def __str__(self): + return '<(%s)>' % getattr(self, 'type', '') + + +class Network(Resource): + + _dates = ('created_at', ) + _maps = {'actor': users.User, 'repo': repos.Repo, 'org': orgs.Org} + + def __str__(self): + return '<(%s)>' % getattr(self, 'type', '') + + +class Org(Resource): + + _dates = ('created_at', ) + _maps = {'actor': users.User, 'repo': repos.Repo, 'org': orgs.Org} + + def __str__(self): + return '<(%s)>' % getattr(self, 'type', '') + + +class User(Resource): + + _dates = ('created_at', ) + _maps = {'actor': users.User, 'repo': repos.Repo, 'org': orgs.Org} + + def __str__(self): + return '<(%s)>' % getattr(self, 'type', '') diff --git a/pygithub3/resources/gists.py b/pygithub3/resources/gists.py index 7e9550a..feb39c1 100644 --- a/pygithub3/resources/gists.py +++ b/pygithub3/resources/gists.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- encoding: utf-8 -*- from .base import Resource @@ -15,6 +14,7 @@ class Fork(Resource): _dates = ('created_at', ) _maps = {'user': User} + def __str__(self): return '' @@ -27,6 +27,7 @@ class History(Resource): def __str__(self): return '' % getattr(self, 'version', '') + class Gist(Resource): _dates = ('created_at', ) @@ -36,6 +37,7 @@ class Gist(Resource): def __str__(self): return '' % getattr(self, 'description', '') + class Comment(Resource): _dates = ('created_at', ) diff --git a/pygithub3/resources/git_data.py b/pygithub3/resources/git_data.py new file mode 100644 index 0000000..4d12e01 --- /dev/null +++ b/pygithub3/resources/git_data.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +from .base import Resource +from .repos import Author, Commit + + +class Blob(Resource): + def __str__(self): + return "" % getattr(self, 'content', '') + + +class Reference(Resource): + def __str__(self): + return '' % getattr(self, 'ref', '') + + +class Tag(Resource): + _maps = {'object': Commit, + 'tagger': Author} # committer? tagger? + + def __str__(self): + return '' % getattr(self, 'tag', '') + + +class Tree(Resource): + def __str__(self): + return '' % getattr(self, 'sha', '') diff --git a/pygithub3/resources/issues.py b/pygithub3/resources/issues.py new file mode 100644 index 0000000..b2301a6 --- /dev/null +++ b/pygithub3/resources/issues.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +import re + +from .base import Resource +from .users import User +from .pull_requests import PullRequest + + +class Label(Resource): + + @staticmethod + def is_valid_color(color): + valid_color = re.compile(r'[0-9abcdefABCDEF]{6}') + match = valid_color.match(color) + if match is None: + return False + return match.start() == 0 and match.end() == len(color) + + def __str__(self): + return '