From c23698e64f3244636290ffa4065f3b70ba5151e9 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 29 Oct 2011 16:11:27 +0200 Subject: Wip on handlers --- github3/api.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) (limited to 'github3/api.py') diff --git a/github3/api.py b/github3/api.py index 81ae3e7..7b63b2d 100644 --- a/github3/api.py +++ b/github3/api.py @@ -18,8 +18,7 @@ from .packages.link_header import parse_link_value from .models import * from .helpers import is_collection, to_python, to_api, get_scope from .config import settings - - +import handlers PAGING_SIZE = 100 @@ -199,24 +198,13 @@ class Github(GithubCore): super(Github, self).__init__() self.is_authenticated = False - - def get_user(self, username): - """Get a single user.""" - return self._get_resource(('users', username), User) - - - def get_me(self): - """Get the authenticated user.""" - return self._get_resource(('user'), CurrentUser) - - def get_repo(self, username, reponame): - """Get the given repo.""" - return self._get_resource(('repos', username, reponame), Repo) - - def get_org(self, login): - """Get organization.""" - return self._get_resource(('orgs', login), Org) - + def user_handler(self, username=None): + if not getattr(self, '_user_handler'): + if self.is_authenticated: + self._user_handler = handlers.AuthUser(self) + else: + self._user_handler = handlers.User(self, username) + return self._user_handler class ResponseError(Exception): """The API Response was unexpected.""" -- cgit v1.2.3-59-g8ed1b From b17dbea56f59aa5403e5722e12878c7183742551 Mon Sep 17 00:00:00 2001 From: David Medina Date: Tue, 1 Nov 2011 12:30:25 +0100 Subject: Decouple Handlers and Models Handler User complete --- github3/api.py | 18 ++--- github3/handlers/__init__.py | 1 + github3/handlers/base.py | 3 +- github3/handlers/user.py | 19 ++++- github3/helpers.py | 19 +++++ github3/models/__init__.py | 4 + github3/models/base.py | 187 ++++++++++++++++++------------------------- github3/models/gists.py | 49 ++++++++++++ github3/models/orgs.py | 28 +++++++ github3/models/repos.py | 30 +++++++ github3/models/user.py | 49 +++--------- 11 files changed, 244 insertions(+), 163 deletions(-) create mode 100644 github3/models/gists.py create mode 100644 github3/models/orgs.py create mode 100644 github3/models/repos.py (limited to 'github3/api.py') diff --git a/github3/api.py b/github3/api.py index 7b63b2d..d99682a 100644 --- a/github3/api.py +++ b/github3/api.py @@ -15,7 +15,6 @@ from decorator import decorator from .packages import omnijson as json from .packages.link_header import parse_link_value -from .models import * from .helpers import is_collection, to_python, to_api, get_scope from .config import settings import handlers @@ -170,6 +169,9 @@ class GithubCore(object): page += 1 + def _get_bool(self, resource): + resp = self._http_resource('GET', resource, check_status=False) + return True if resp.status_code == 204 else False def _to_map(self, obj, iterable): """Maps given dict iterable to a given Resource object.""" @@ -181,16 +183,6 @@ class GithubCore(object): return a - def _get_url(self, resource): - - if is_collection(resource): - resource = map(str, resource) - resource = '/'.join(resource) - - return resource - - - class Github(GithubCore): """docstring for Github""" @@ -198,8 +190,8 @@ class Github(GithubCore): super(Github, self).__init__() self.is_authenticated = False - def user_handler(self, username=None): - if not getattr(self, '_user_handler'): + def user_handler(self, username=None, force=False): + if force or not getattr(self, '_user_handler', False): if self.is_authenticated: self._user_handler = handlers.AuthUser(self) else: diff --git a/github3/handlers/__init__.py b/github3/handlers/__init__.py index e69de29..126efcb 100644 --- a/github3/handlers/__init__.py +++ b/github3/handlers/__init__.py @@ -0,0 +1 @@ +from user import AuthUser, User diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 6a5ac65..93a4680 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -3,8 +3,6 @@ # # author: David Medina -import models - class Handler(object): """ Abstract handler, that inject github.api """ @@ -24,3 +22,4 @@ class Handler(object): url = self._extend_url(*args) map_model = kwargs.get('model', self._model) return self._gh._get_resources(url, map_model, **kwargs) + diff --git a/github3/handlers/user.py b/github3/handlers/user.py index 16f8363..c59607d 100644 --- a/github3/handlers/user.py +++ b/github3/handlers/user.py @@ -4,6 +4,7 @@ # author: David Medina from .base import Handler +import github3.models as models class User(Handler): """ Handler to query public user api """ @@ -13,7 +14,7 @@ class User(Handler): raise exceptions.AnomUser("%s need a username" % self.__class__) self._url = ('users', username) - self._model = models.AnomUser + self._model = models.User self.username = username super(User, self).__init__(gh) @@ -29,12 +30,24 @@ class User(Handler): def get_following(self, limit=None): return self._get_resources('following') -class AuthUser(AnomUser): + def get_repos(self, limit=None): + return self._get_resources('repos', model=models.Repo) + + def get_watched(self, limit=None): + return self._get_resources('watched', model=models.Repo) + + def get_orgs(self, limit=None): + return self._get_resources('orgs', model=models.Org) + + def get_gists(self, limit=None): + return self._get_resources('gists', model=models.Gist) + +class AuthUser(User): """ Handler to query public/private api for authenticated user """ def __init__(self, gh): self._url = ('user',) - self._model = models.User + self._model = models.AuthUser super(AnomUser, self).__init__(gh) def __repr__(self): diff --git a/github3/helpers.py b/github3/helpers.py index 498a005..205e097 100644 --- a/github3/helpers.py +++ b/github3/helpers.py @@ -57,6 +57,7 @@ def to_python(obj, date_keys=None, int_keys=None, object_map=None, + list_map=None, bool_keys=None, **kwargs): """Extends a given object for API Consumption. @@ -96,14 +97,32 @@ def to_python(obj, if object_map: for (k, v) in object_map.items(): if in_dict.get(k): + if v == 'self': + v = obj.__class__ d[k] = v.new_from_dict(in_dict.get(k)) + if list_map: + for k, model in list_map.items(): + nested_map = in_dict.get(k) + if nested_map: + if getattr(nested_map, 'items', False): + map_dict = {} + for nested_item, nested_dict in nested_map.items(): + map_dict[nested_item] = model.new_from_dict(nested_dict) + d[k] = map_dict + else: + map_list = [] + for item_map in nested_map: + map_list.append(model.new_from_dict(item_map)) + d[k] = map_list + obj.__dict__.update(d) obj.__dict__.update(kwargs) # Save the dictionary, for write comparisons. obj._cache = d obj.__cache = in_dict + obj.post_map() return obj diff --git a/github3/models/__init__.py b/github3/models/__init__.py index e69de29..6e4db1b 100644 --- a/github3/models/__init__.py +++ b/github3/models/__init__.py @@ -0,0 +1,4 @@ +from .user import AuthUser, User +from .repos import Repo +from .orgs import Org +from .gists import Gist diff --git a/github3/models/base.py b/github3/models/base.py index 618e57f..1b1ce89 100644 --- a/github3/models/base.py +++ b/github3/models/base.py @@ -6,9 +6,9 @@ This module provides the Github3 object model. """ import json +import inspect -from .helpers import to_python, to_api, key_diff - +from github3.helpers import to_python, to_api, key_diff class BaseResource(object): """A BaseResource object.""" @@ -18,15 +18,24 @@ class BaseResource(object): _dates = [] _bools = [] _map = {} + _list_map = {} _writeable = [] _cache = {} + def post_map(self): + try: + handler = self.handler() + methods = filter(lambda x: x[0].startswith('get') and callable(x), + inspect.getmembers(handler, inspect.ismethod)) + for name, callback in methods: + setattr(self, method, callback) + except: + pass def __init__(self): self._bootstrap() super(BaseResource, self).__init__() - def __dir__(self): return self.keys() @@ -56,6 +65,7 @@ class BaseResource(object): date_keys = cls._dates, bool_keys = cls._bools, object_map = cls._map, + list_map = cls._list_map, _gh = gh ) @@ -70,109 +80,70 @@ class BaseResource(object): return r -class Plan(BaseResource): - """Github Plan object model.""" - - _strs = ['name'] - _ints = ['space', 'collaborators', 'private_repos'] - - def __repr__(self): - return ''.format(str(self.name)) - -class Org(BaseResource): - """Github Organization object model.""" - - _strs = [ - 'login', 'url', 'avatar_url', 'name', 'company', 'blog', 'location', 'email' - 'html_url', 'type', 'billing_email'] - _ints = [ - 'id', 'public_repos', 'public_gists', 'followers', 'following', - 'total_private_repos', 'owned_private_repos', 'private_gists', 'disk_usage', - 'collaborators'] - _dates = ['created_at'] - _map = {'plan': Plan} - _writable = ['billing_email', 'blog', 'company', 'email', 'location', 'name'] - - @property - def ri(self): - return ('orgs', self.login) - - def __repr__(self): - return ''.format(self.login) - - def repos(self, limit=None): - return self._gh._get_resources(('orgs', self.login, 'repos'), Repo, limit=limit) - - def members(self, limit=None): - return self._gh._get_resources(('orgs', self.login, 'members'), User, limit=limit) - - def is_member(self, username): - if isinstance(username, User): - username = username.login - - r = self._gh._http_resource('GET', ('orgs', self.login, 'members', username), check_status=False) - return (r.status_code == 204) - - def publicize_member(self, username): - if isinstance(username, User): - username = username.login - - r = self._gh._http_resource('PUT', ('orgs', self.login, 'public_members', username), check_status=False, data='') - return (r.status_code == 204) - - def conceal_member(self, username): - if isinstance(username, User): - username = username.login - - r = self._gh._http_resource('DELETE', ('orgs', self.login, 'public_members', username), check_status=False) - return (r.status_code == 204) - - def remove_member(self, username): - if isinstance(username, User): - username = username.login - - r = self._gh._http_resource('DELETE', ('orgs', self.login, 'members', username), check_status=False) - return (r.status_code == 204) - - def public_members(self, limit=None): - return self._gh._get_resources(('orgs', self.login, 'public_members'), User, limit=limit) - - def is_public_member(self, username): - if isinstance(username, User): - username = username.login - - r = self._gh._http_resource('GET', ('orgs', self.login, 'public_members', username), check_status=False) - return (r.status_code == 204) - - -class Gist(BaseResource): - _strs = ['url', 'description', 'html_url', 'git_pull_url', 'git_push_url'] - _ints = ['id', 'comments'] - _bools = ['public'] - _dates = ['created_at'] - _map = {'user': User} #TODO: file - - @property - def ri(self): - return ('users', self.user.login, self.id) - - def __repr__(self): - return '' % (self.user.login, self.description) - -class Repo(BaseResource): - _strs = [ - 'url', 'html_url', 'clone_url', 'git_url', 'ssh_url', 'svn_url', - 'name', 'description', 'homepage', 'language', 'master_branch'] - _bools = ['private', 'fork'] - _ints = ['forks', 'watchers', 'size',] - _dates = ['pushed_at', 'created_at'] - _map = {'owner': User} - - - @property - def ri(self): - return ('repos', self.owner.login, self.name) - def __repr__(self): - return ''.format(self.owner.login, self.name) - # owner +#class Org(BaseResource): +# """Github Organization object model.""" +# +# _strs = [ +# 'login', 'url', 'avatar_url', 'name', 'company', 'blog', 'location', 'email' +# 'html_url', 'type', 'billing_email'] +# _ints = [ +# 'id', 'public_repos', 'public_gists', 'followers', 'following', +# 'total_private_repos', 'owned_private_repos', 'private_gists', 'disk_usage', +# 'collaborators'] +# _dates = ['created_at'] +# _map = {'plan': Plan} +# _writable = ['billing_email', 'blog', 'company', 'email', 'location', 'name'] +# +# @property +# def ri(self): +# return ('orgs', self.login) +# +# def __repr__(self): +# return ''.format(self.login) +# +# def repos(self, limit=None): +# return self._gh._get_resources(('orgs', self.login, 'repos'), Repo, limit=limit) +# +# def members(self, limit=None): +# return self._gh._get_resources(('orgs', self.login, 'members'), User, limit=limit) +# +# def is_member(self, username): +# if isinstance(username, User): +# username = username.login +# +# r = self._gh._http_resource('GET', ('orgs', self.login, 'members', username), check_status=False) +# return (r.status_code == 204) +# +# def publicize_member(self, username): +# if isinstance(username, User): +# username = username.login +# +# r = self._gh._http_resource('PUT', ('orgs', self.login, 'public_members', username), check_status=False, data='') +# return (r.status_code == 204) +# +# def conceal_member(self, username): +# if isinstance(username, User): +# username = username.login +# +# r = self._gh._http_resource('DELETE', ('orgs', self.login, 'public_members', username), check_status=False) +# return (r.status_code == 204) +# +# def remove_member(self, username): +# if isinstance(username, User): +# username = username.login +# +# r = self._gh._http_resource('DELETE', ('orgs', self.login, 'members', username), check_status=False) +# return (r.status_code == 204) +# +# def public_members(self, limit=None): +# return self._gh._get_resources(('orgs', self.login, 'public_members'), User, limit=limit) +# +# def is_public_member(self, username): +# if isinstance(username, User): +# username = username.login +# +# r = self._gh._http_resource('GET', ('orgs', self.login, 'public_members', username), check_status=False) +# return (r.status_code == 204) +# +# diff --git a/github3/models/gists.py b/github3/models/gists.py new file mode 100644 index 0000000..5ad61c3 --- /dev/null +++ b/github3/models/gists.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +from .base import BaseResource +from .user import User + +class File(BaseResource): + _strs = ['filename', 'raw_url', 'content', 'language', 'type'] + _ints = ['size'] + + def __repr__(self): + return ' %s' % self.filename + +class GistFork(BaseResource): + _strs = ['url'] + _dates = ['created_at'] + _map = {'user': User} + + def __repr__(self): + return ' %s>' % self.user.login + +class ChangeStatus(BaseResource): + _ints = ['deletions', 'additions', 'total'] + + def __repr__(self): + return ' change_status>' + +class GistHistory(BaseResource): + _strs = ['url', 'version'] + _map = {'user': User, 'change_status': ChangeStatus} + _dates = ['committed_at'] + +class Gist(BaseResource): + _strs = ['url', 'description', 'html_url', 'git_pull_url', 'git_push_url'] + _ints = ['id', 'comments'] + _bools = ['public'] + _dates = ['created_at'] + _map = {'user': User} + _list_map = {'files': File, 'forks': GistFork, 'history': GistHistory} + + @property + def ri(self): + return ('users', self.user.login, self.id) + + def __repr__(self): + return '' % (self.user.login, self.description) + diff --git a/github3/models/orgs.py b/github3/models/orgs.py new file mode 100644 index 0000000..1ce638e --- /dev/null +++ b/github3/models/orgs.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +from .base import BaseResource +from .user import Plan + +class Org(BaseResource): + """Github Organization object model.""" + + _strs = [ + 'login', 'url', 'avatar_url', 'name', 'company', 'blog', 'location', 'email' + 'html_url', 'type', 'billing_email'] + _ints = [ + 'id', 'public_repos', 'public_gists', 'followers', 'following', + 'total_private_repos', 'owned_private_repos', 'private_gists', 'disk_usage', + 'collaborators'] + _dates = ['created_at'] + _map = {'plan': Plan} + _writable = ['billing_email', 'blog', 'company', 'email', 'location', 'name'] + + @property + def ri(self): + return ('orgs', self.login) + + def __repr__(self): + return ''.format(self.login) diff --git a/github3/models/repos.py b/github3/models/repos.py new file mode 100644 index 0000000..8dbe970 --- /dev/null +++ b/github3/models/repos.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +from .base import BaseResource +from .user import User +from .orgs import Org + +class Repo(BaseResource): + _strs = [ + 'url', 'html_url', 'clone_url', 'git_url', 'ssh_url', 'svn_url', + 'name', 'description', 'homepage', 'language', 'master_branch'] + _bools = ['private', 'fork', 'has_issues', 'has_wiki', 'has_downloads'] + _ints = ['forks', 'watchers', 'size', 'open_issues'] + _dates = ['pushed_at', 'created_at'] + _map = { + 'owner': User, + 'organization': Org, + 'parent': 'self', + 'source': 'self', + } + + @property + def ri(self): + return ('repos', self.owner.login, self.name) + + def __repr__(self): + return ''.format(self.owner.login, self.name) + # owner diff --git a/github3/models/user.py b/github3/models/user.py index cdbd04f..d58d9b2 100644 --- a/github3/models/user.py +++ b/github3/models/user.py @@ -5,18 +5,25 @@ from .base import BaseResource +class Plan(BaseResource): + """Github Plan object model.""" + + _strs = ['name'] + _ints = ['space', 'collaborators', 'private_repos'] + + def __repr__(self): + return ''.format(str(self.name)) + class User(BaseResource): """Github User object model.""" _strs = [ 'login','avatar_url', 'url', 'name', 'company', 'blog', 'location', - 'email', 'bio', 'html_url'] + 'email', 'bio', 'html_url', 'type'] _ints = ['id', 'public_repos', 'public_gists', 'followers', 'following'] _dates = ['created_at',] _bools = ['hireable', ] - # _map = {} - # _writeable = [] @property def ri(self): @@ -25,26 +32,8 @@ class User(BaseResource): def __repr__(self): return ''.format(self.login) - @property def handler(self): - return self._gh.user_handler(self.login) - - def get_followers(self, limit=None): - return self.handler.followers(limit) - - def get_following(self, limit=None): - return self.handler.following(limit) - - def repos(self, limit=None): - return self._gh._get_resources(('users', self.login, 'repos'), Repo, limit=limit) - def repo(self, reponame): - return self._gh._get_resource(('repos', self.login, reponame), Repo) - - def orgs(self): - return self._gh._get_resources(('users', self.login, 'orgs'), Org) - - def gists(self): - return self._gh._get_resources(('users', self.login, 'gists'), Gist) + return self._gh.user_handler(self.login, force=True) class AuthUser(User): """Github Current User object model.""" @@ -61,19 +50,5 @@ class AuthUser(User): return ('user',) def __repr__(self): - return ''.format(self.login) - - def repos(self, limit=None): - return self._gh._get_resources(('user', 'repos'), Repo, limit=limit) - - def repo(self, reponame): - return self._gh._get_resource(('repos', self.login, reponame), Repo) - - def orgs(self, limit=None): - return self._gh._get_resources(('user', 'orgs'), Org, limit=limit) - - def org(self, orgname): - return self._gh._get_resource(('orgs', orgname), Org) + return ''.format(self.login) - def gists(self, limit=None): - return self._gh._get_resources('gists', Gist, limit=limit) -- cgit v1.2.3-59-g8ed1b From 15b7cc0fe44d02a9f2271041cd69140d63279ff1 Mon Sep 17 00:00:00 2001 From: David Medina Date: Tue, 1 Nov 2011 19:24:15 +0100 Subject: Fix names. Added raw request Think about return user handler more elegant --- github3/api.py | 10 +++++++--- github3/handlers/base.py | 4 ++++ github3/handlers/user.py | 15 +++++++++++---- github3/models/user.py | 7 +++++-- 4 files changed, 27 insertions(+), 9 deletions(-) (limited to 'github3/api.py') diff --git a/github3/api.py b/github3/api.py index d99682a..b0a2c96 100644 --- a/github3/api.py +++ b/github3/api.py @@ -173,6 +173,10 @@ class GithubCore(object): resp = self._http_resource('GET', resource, check_status=False) return True if resp.status_code == 204 else False + def _get_raw(self, resource): + resp = self._http_resource('GET', resource) + return self._resource_deserialize(resp.content) + def _to_map(self, obj, iterable): """Maps given dict iterable to a given Resource object.""" @@ -190,9 +194,9 @@ class Github(GithubCore): super(Github, self).__init__() self.is_authenticated = False - def user_handler(self, username=None, force=False): - if force or not getattr(self, '_user_handler', False): - if self.is_authenticated: + def user_handler(self, username=None, **kwargs): + if kwargs.get('force') or not getattr(self, '_user_handler', False): + if kwargs.get('private'): self._user_handler = handlers.AuthUser(self) else: self._user_handler = handlers.User(self, username) diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 93a4680..3bd4bd1 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -13,6 +13,10 @@ class Handler(object): def _extend_url(self, *args): return self._url + args + def _get_raw(self, *args, **kwargs): + url = self._extend_url(*args) + return self._gh._get_raw(url, **kwargs) + def _get_resource(self, *args, **kwargs): url = self._extend_url(*args) map_model = kwargs.get('model', self._model) diff --git a/github3/handlers/user.py b/github3/handlers/user.py index 09ea031..47f1a92 100644 --- a/github3/handlers/user.py +++ b/github3/handlers/user.py @@ -20,7 +20,7 @@ class User(Handler): super(User, self).__init__(gh) def __repr__(self): - return ' %s' % self.username + return ' %s' % self.username def get(self): return self._get_resource() @@ -48,8 +48,15 @@ class AuthUser(User): def __init__(self, gh): self._url = ('user',) - self._model = models.AuthUser - super(AnomUser, self).__init__(gh) + self._model = models.User + super(User, self).__init__(gh) def __repr__(self): - return '' + return '' + + def get(self): + return self._get_resource(model=models.AuthUser) + + def get_emails(self): + return self._get_raw('emails') + diff --git a/github3/models/user.py b/github3/models/user.py index d58d9b2..7c730f6 100644 --- a/github3/models/user.py +++ b/github3/models/user.py @@ -30,7 +30,7 @@ class User(BaseResource): return ('users', self.login) def __repr__(self): - return ''.format(self.login) + return ''.format(self.login) def handler(self): return self._gh.user_handler(self.login, force=True) @@ -45,10 +45,13 @@ class AuthUser(User): _map = {'plan': Plan} _writeable = ['name', 'email', 'blog', 'company', 'location', 'hireable', 'bio'] + def handler(self): + return self._gh.user_handler(self.login, force=True, private=True) + @property def ri(self): return ('user',) def __repr__(self): - return ''.format(self.login) + return ''.format(self.login) -- cgit v1.2.3-59-g8ed1b From d57caebf9c3fd5f9ae8611c8de576ec6eb1a070a Mon Sep 17 00:00:00 2001 From: David Medina Date: Tue, 1 Nov 2011 22:20:39 +0100 Subject: Fix bug generate_url --- github3/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'github3/api.py') diff --git a/github3/api.py b/github3/api.py index b0a2c96..8b2e6e8 100644 --- a/github3/api.py +++ b/github3/api.py @@ -54,7 +54,7 @@ class GithubCore(object): if is_collection(endpoint): resource = map(str, endpoint) - resource = '/'.join(endpoint) + resource = '/'.join(resource) else: resource = endpoint -- cgit v1.2.3-59-g8ed1b From bb540725ce15dcafd5ac5e5bb93e5a232ff4f33a Mon Sep 17 00:00:00 2001 From: David Medina Date: Fri, 4 Nov 2011 01:21:50 +0100 Subject: Refactor base to handler design --- github3/__init__.py | 2 +- github3/api.py | 255 +++++++++++------------------------------------ github3/config.py | 4 +- github3/errors.py | 34 +++++++ github3/exceptions.py | 6 ++ github3/handlers/base.py | 8 ++ github3/handlers/user.py | 7 ++ 7 files changed, 116 insertions(+), 200 deletions(-) create mode 100644 github3/errors.py (limited to 'github3/api.py') diff --git a/github3/__init__.py b/github3/__init__.py index c2fdbad..a99ea00 100644 --- a/github3/__init__.py +++ b/github3/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from core import * \ No newline at end of file +#from core import * diff --git a/github3/api.py b/github3/api.py index 8b2e6e8..3376bea 100644 --- a/github3/api.py +++ b/github3/api.py @@ -1,207 +1,68 @@ -# -*- coding: utf-8 -*- - -""" -github3.api -~~~~~~~~~~~ - -This module provies the core GitHub3 API interface. -""" - -from urlparse import urlparse, parse_qs +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina import requests -from decorator import decorator - -from .packages import omnijson as json -from .packages.link_header import parse_link_value - -from .helpers import is_collection, to_python, to_api, get_scope -from .config import settings -import handlers +import json +from errors import GithubError +import github3.exceptions as ghexceptions - -PAGING_SIZE = 100 +RESOURCES_PER_PAGE = 100 class GithubCore(object): + """ Wrapper for requests """ - _rate_limit = None - _rate_limit_remaining = None + requests_remaining = None def __init__(self): self.session = requests.session() - self.session.params = {'per_page': PAGING_SIZE} - - - @staticmethod - def _resource_serialize(o): - """Returns JSON serialization of given object.""" - return json.dumps(o) - - - @staticmethod - def _resource_deserialize(s): - """Returns dict deserialization of a given JSON string.""" - + self.session.params = {'per_page': RESOURCES_PER_PAGE} + self._parser = json + + #@paginate to slice a generator after + def get(self, request, **kwargs): + response = self._request('GET', request, **kwargs) + return self._parser.loads(response.content) + + def head(self, request, **kwargs): + return self._request('HEAD', request, **kwargs).headers + + def post(self, request, data=None, **kwargs): + kwargs['data'] = self._parser.dumps(data) + response = self._request('POST', request, **kwargs) + assert response.status_code == 201 + return self._parser.loads(response.content) + + def patch(self, request, data=None, **kwargs): + kwargs['data'] = self._parser.dumps(data) + response = self._request('PATCH', request, **kwargs) + assert response.status_code == 200 + return self._parser.loads(response.content) + + def put(self, request, **kwargs): + response = self._request('PUT', request, **kwargs) + assert response.status_code == 204 + + def bool(self, request, **kwargs): try: - return json.loads(s) - except ValueError: - raise ResponseError('The API Response was not valid.') - - - @staticmethod - def _generate_url(endpoint): - """Generates proper endpoint URL.""" - - if is_collection(endpoint): - resource = map(str, endpoint) - resource = '/'.join(resource) - else: - resource = endpoint - - return (settings.base_url + resource) - - - def _requests_post_hook(self, r): - """Post-processing for HTTP response objects.""" - - self._rate_limit = int(r.headers.get('x-ratelimit-limit', -1)) - self._rate_limit_remaining = int(r.headers.get('x-ratelimit-remaining', -1)) - - return r - - - def _http_resource(self, verb, endpoint, params=None, check_status=True, **etc): - - url = self._generate_url(endpoint) - args = (verb, url) - - if params: - kwargs = {'params': params} - kwargs.update(etc) - else: - kwargs = etc - - r = self.session.request(*args, **kwargs) - r = self._requests_post_hook(r) - - if check_status: - r.raise_for_status() - - return r - - - def _get_resource(self, resource, obj, **kwargs): - - r = self._http_resource('GET', resource, params=kwargs) - item = self._resource_deserialize(r.content) - - return obj.new_from_dict(item, gh=self) - - def _patch_resource(self, resource, data, **kwargs): - r = self._http_resource('PATCH', resource, data=data, params=kwargs) - msg = self._resource_deserialize(r.content) - - return msg - - - @staticmethod - def _total_pages_from_header(link_header): - - if link_header is None: - return link_header - - page_info = {} - - for link in link_header.split(','): - - uri, meta = map(str.strip, link.split(';')) - - # Strip <>'s - uri = uri[1:-1] - - # Get query params from header. - q = parse_qs(urlparse(uri).query) - meta = meta[5:-1] - - page_info[meta] = q - - try: - return int(page_info['last']['page'].pop()) - except KeyError: - return True - - def _get_resources(self, resource, obj, limit=None, **kwargs): - - if limit is not None: - assert limit > 0 - - moar = True - is_truncated = (limit > PAGING_SIZE) or (limit is None) - r_count = 0 - page = 1 - - while moar: - - if not is_truncated: - kwargs['per_page'] = limit - moar = False - else: - kwargs['page'] = page - if limit: - if (limit - r_count) < PAGING_SIZE: - kwargs['per_page'] = (limit - r_count) - moar = False - - r = self._http_resource('GET', resource, params=kwargs) - max_page = self._total_pages_from_header(r.headers['link']) - - if (max_page is True) or (max_page is None): - moar = False - - d_items = self._resource_deserialize(r.content) - - for item in d_items: - if (r_count < limit) or (limit is None): - r_count += 1 - yield obj.new_from_dict(item, gh=self) - else: - moar = False - - page += 1 - - def _get_bool(self, resource): - resp = self._http_resource('GET', resource, check_status=False) - return True if resp.status_code == 204 else False - - def _get_raw(self, resource): - resp = self._http_resource('GET', resource) - return self._resource_deserialize(resp.content) - - def _to_map(self, obj, iterable): - """Maps given dict iterable to a given Resource object.""" - - a = list() - - for it in iterable: - a.append(obj.new_from_dict(it, rdd=self)) - - return a - -class Github(GithubCore): - """docstring for Github""" - - def __init__(self): - super(Github, self).__init__() - self.is_authenticated = False - - def user_handler(self, username=None, **kwargs): - if kwargs.get('force') or not getattr(self, '_user_handler', False): - if kwargs.get('private'): - self._user_handler = handlers.AuthUser(self) - else: - self._user_handler = handlers.User(self, username) - return self._user_handler - -class ResponseError(Exception): - """The API Response was unexpected.""" - + response = self._request('GET', request, **kwargs) + except ghexceptions.NotFound: + return False + assert response.status_code == 204 + return True + + def delete(self, request, **kwargs): + response = self._request('DELETE', request, **kwargs) + assert response.status_code == 204 + + def _request(self, verb, request, **kwargs): + + request = settings.base_url + request + response = self.session.request(verb, request, **kwargs) + self.requests_remaining = response.headers.get( + 'x-ratelimit-remaining',-1) + error = GithubError(response) + error.process() + + return response diff --git a/github3/config.py b/github3/config.py index 9fbf305..6fec55f 100644 --- a/github3/config.py +++ b/github3/config.py @@ -54,5 +54,5 @@ class Settings(object): return object.__getattribute__(self, key) settings = Settings() -settings.verbose = False -settings.base_url = 'https://api.github.com/' \ No newline at end of file +settings.verbose = True +settings.base_url = 'https://api.github.com/' diff --git a/github3/errors.py b/github3/errors.py new file mode 100644 index 0000000..6932360 --- /dev/null +++ b/github3/errors.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +import json +import github3.exceptions as exceptions + +class GithubError(object): + """ Handler for API errors """ + + def __init__(self, response): + self._parser = json + self.status_code = response.status_code + self.debug = self._parser.loads(response.content) + + def error_400(self): + return exceptions.BadRequest("400 - %s" % self.debug.get('message')) + + def error_404(self) + return exceptions.NotFound("404 - %s" % self.debug.get('message')) + + def error_422(self): + errors = self.debug.get('errors') + if errors: + errors = ['{resource}: {code} => {field}'.format(**error) + for error in errors] + return exceptions.UnprocessableEntity( + '422 - %s %s' % (self.debug.get('message'), errors)) + + def process(self): + raise_error = getattr(self, 'error_%s' % self.status_code, False) + if raise_error: + raise raise_error() diff --git a/github3/exceptions.py b/github3/exceptions.py index 72da776..b0894a9 100644 --- a/github3/exceptions.py +++ b/github3/exceptions.py @@ -3,6 +3,12 @@ # # author: David Medina +class BadRequest(Exception): + pass +class UnprocessableEntity(Exception): + pass +class NotFound(Exception): + pass class AnomUser(Exception): """ Exception for AnomUser handler """ pass diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 92cd73a..8f79cdd 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -31,3 +31,11 @@ class Handler(object): map_model = kwargs.get('model', self._model) return self._gh._get_resources(url, map_model, **kwargs) + def _post_raw(self, *args, **kwargs): + url = self._extend_url(*args) + return self._gh._post_raw(url, **kwargs) + + def _delete_raw(self, *args, **kwargs): + url = self._extend_url(*args) + return self._gh._delete_raw(url, **kwargs) + diff --git a/github3/handlers/user.py b/github3/handlers/user.py index 5f5d130..5018aa1 100644 --- a/github3/handlers/user.py +++ b/github3/handlers/user.py @@ -72,3 +72,10 @@ class AuthUser(User): def get_key(self, key_id): return self._get_resource('keys', key_id, model=models.Key) + def post_emails(self, *emails): + emails_parsed = map(str, emails) + return self._post_raw('emails', data=emails_parsed) + + def delete_emails(self, *emails): + emails_parsed = map(str, emails) + return self._delete_raw('emails', data=emails_parsed) -- cgit v1.2.3-59-g8ed1b From bdc593ac5ae97f4f9d65df15fcc18b632fae7666 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sat, 5 Nov 2011 20:47:14 +0100 Subject: Fix litle bugs and args to request --- github3/api.py | 33 ++++++++++++++++++++++++++++----- github3/errors.py | 7 +++++-- 2 files changed, 33 insertions(+), 7 deletions(-) (limited to 'github3/api.py') diff --git a/github3/api.py b/github3/api.py index 3376bea..d82bd3a 100644 --- a/github3/api.py +++ b/github3/api.py @@ -14,16 +14,21 @@ class GithubCore(object): """ Wrapper for requests """ requests_remaining = None + base_url = 'https://api.github.com/' def __init__(self): self.session = requests.session() self.session.params = {'per_page': RESOURCES_PER_PAGE} self._parser = json - #@paginate to slice a generator after - def get(self, request, **kwargs): + def get(self, request, paginate=False, **kwargs): + print '\nGET %s %s\n' % (request, kwargs) response = self._request('GET', request, **kwargs) - return self._parser.loads(response.content) + content = self._parser.loads(response.content) + if paginate: + return response.headers.get('link'), content + else: + return content def head(self, request, **kwargs): return self._request('HEAD', request, **kwargs).headers @@ -56,10 +61,28 @@ class GithubCore(object): response = self._request('DELETE', request, **kwargs) assert response.status_code == 204 + def _parse_args(self, request_args): + request_core = ( + 'params','data','headers','cookies','files','auth','tiemout', + 'allow_redirects','proxies','return_response','config') + request_params = request_args.get('params') + extra_params = {} + for k, v in request_args.items(): + if k in request_core: continue + extra_params.update({k: v}) + del request_args[k] + if request_params: + request_args['params'].update(extra_params) + else: + request_args['params'] = extra_params + + return request_args + def _request(self, verb, request, **kwargs): - request = settings.base_url + request - response = self.session.request(verb, request, **kwargs) + request = self.base_url + request + parsed_args = self._parse_args(kwargs) + response = self.session.request(verb, request, **parsed_args) self.requests_remaining = response.headers.get( 'x-ratelimit-remaining',-1) error = GithubError(response) diff --git a/github3/errors.py b/github3/errors.py index 6932360..96693be 100644 --- a/github3/errors.py +++ b/github3/errors.py @@ -12,12 +12,15 @@ class GithubError(object): def __init__(self, response): self._parser = json self.status_code = response.status_code - self.debug = self._parser.loads(response.content) + if response.content: + self.debug = self._parser.loads(response.content) + else: + self.debug = {} def error_400(self): return exceptions.BadRequest("400 - %s" % self.debug.get('message')) - def error_404(self) + def error_404(self): return exceptions.NotFound("404 - %s" % self.debug.get('message')) def error_422(self): -- cgit v1.2.3-59-g8ed1b From df73c78d8ab6628a041c06af2d6183dcb77d1348 Mon Sep 17 00:00:00 2001 From: David Medina Date: Sun, 6 Nov 2011 03:20:02 +0100 Subject: Doc and refactorize --- github3/api.py | 51 +++++++++++++++++++++++++++++++++++++----------- github3/handlers/base.py | 20 ++++++++++++++++++- 2 files changed, 59 insertions(+), 12 deletions(-) (limited to 'github3/api.py') diff --git a/github3/api.py b/github3/api.py index d82bd3a..6c41e53 100644 --- a/github3/api.py +++ b/github3/api.py @@ -6,23 +6,34 @@ import requests import json from errors import GithubError -import github3.exceptions as ghexceptions RESOURCES_PER_PAGE = 100 class GithubCore(object): - """ Wrapper for requests """ + """ + Wrapper to github api requests + + Methods: get, head, post, patch, put, delete + """ requests_remaining = None base_url = 'https://api.github.com/' def __init__(self): + """ + Init `requests.session` + Init JSON parser + """ self.session = requests.session() self.session.params = {'per_page': RESOURCES_PER_PAGE} self._parser = json def get(self, request, paginate=False, **kwargs): - print '\nGET %s %s\n' % (request, kwargs) + """ + GET request + + :param paginate: Boolean to return link header to paginate + """ response = self._request('GET', request, **kwargs) content = self._parser.loads(response.content) if paginate: @@ -31,37 +42,49 @@ class GithubCore(object): return content def head(self, request, **kwargs): + """ HEAD request """ return self._request('HEAD', request, **kwargs).headers def post(self, request, data=None, **kwargs): + """ + POST request + + :param data: raw python object to send + """ kwargs['data'] = self._parser.dumps(data) response = self._request('POST', request, **kwargs) assert response.status_code == 201 return self._parser.loads(response.content) def patch(self, request, data=None, **kwargs): + """ + PATCH request + + :param data: raw python object to send + """ kwargs['data'] = self._parser.dumps(data) response = self._request('PATCH', request, **kwargs) assert response.status_code == 200 return self._parser.loads(response.content) def put(self, request, **kwargs): + """ PUT request """ response = self._request('PUT', request, **kwargs) assert response.status_code == 204 - def bool(self, request, **kwargs): - try: - response = self._request('GET', request, **kwargs) - except ghexceptions.NotFound: - return False - assert response.status_code == 204 - return True - def delete(self, request, **kwargs): + """ DELETE request """ response = self._request('DELETE', request, **kwargs) assert response.status_code == 204 def _parse_args(self, request_args): + """ + Arg's parser to `_request` method + + It check keyword args to parse extra request args to params + Sample: + _parse_args(arg1=1, arg2=2) => params = {'arg1': 1, 'arg2': 2} + """ request_core = ( 'params','data','headers','cookies','files','auth','tiemout', 'allow_redirects','proxies','return_response','config') @@ -79,7 +102,13 @@ class GithubCore(object): return request_args def _request(self, verb, request, **kwargs): + """ + Http request wrapper + :param verb: Http method + :param request : Url query request + :param kwargs: Keyword args to request + """ request = self.base_url + request parsed_args = self._parse_args(kwargs) response = self.session.request(verb, request, **parsed_args) diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 5da7752..3f82817 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -3,8 +3,14 @@ # # author: David Medina +import github3.exceptions as ghexceptions + class Paginate: - """ Paginate resources """ + """ Paginate resource iterator + + :param resource: URL resource + :param requester: Bound method to request. See `GithubCore.get` + """ def __init__(self, resource, requester): self.resource = resource @@ -12,6 +18,7 @@ class Paginate: self.page = 1 def _last_page(self, link): + """ Get and cached last page from link header """ if not getattr(self, 'last', False): from github3.packages.link_header import parse_link_value from urlparse import urlparse, parse_qs @@ -26,6 +33,7 @@ class Paginate: return self def initial(self): + """ First request. Force requester to paginate returning link header """ link, content = self.requester(self.resource, paginate=True, page=1) self.last = self._last_page(link) if link else 1 return content @@ -50,8 +58,18 @@ class Handler(object): self._gh = gh super(Handler, self).__init__() + def _bool(self, resource, **kwargs): + """ Handler request to boolean response """ + try: + response = self._gh.head(resource, **kwargs) + except ghexceptions.NotFound: + return False + assert response.status_code == 204 + return True + #TODO: if limit is multiple of per_page... it do another request for nothing def _get_resources(self, resource, model=None, limit=None): + """ Hander request to multiple resources """ page_resources = Paginate(resource, self._gh.get) counter = 1 for page in page_resources: -- cgit v1.2.3-59-g8ed1b From 79b46e920e1f7462ffad6100dad5ed3fa6d9009a Mon Sep 17 00:00:00 2001 From: David Medina Date: Sun, 6 Nov 2011 03:43:04 +0100 Subject: Cleaning files from origin repo --- README.rst | 6 +- github3/__init__.py | 2 +- github3/api.py | 3 + github3/config.py | 58 --- github3/core.py | 29 +- github3/handlers/__init__.py | 1 - github3/handlers/user.old.py | 81 ++++ github3/handlers/user.py | 81 ---- github3/packages/omnijson/__init__.py | 13 - github3/packages/omnijson/core.py | 93 ---- github3/packages/omnijson/packages/__init__.py | 0 .../omnijson/packages/simplejson/__init__.py | 438 ------------------ .../omnijson/packages/simplejson/decoder.py | 421 ----------------- .../omnijson/packages/simplejson/encoder.py | 503 --------------------- .../omnijson/packages/simplejson/ordered_dict.py | 119 ----- .../omnijson/packages/simplejson/scanner.py | 70 --- reqs.txt | 2 - 17 files changed, 91 insertions(+), 1829 deletions(-) delete mode 100644 github3/config.py create mode 100644 github3/handlers/user.old.py delete mode 100644 github3/handlers/user.py delete mode 100644 github3/packages/omnijson/__init__.py delete mode 100644 github3/packages/omnijson/core.py delete mode 100644 github3/packages/omnijson/packages/__init__.py delete mode 100644 github3/packages/omnijson/packages/simplejson/__init__.py delete mode 100644 github3/packages/omnijson/packages/simplejson/decoder.py delete mode 100644 github3/packages/omnijson/packages/simplejson/encoder.py delete mode 100644 github3/packages/omnijson/packages/simplejson/ordered_dict.py delete mode 100644 github3/packages/omnijson/packages/simplejson/scanner.py (limited to 'github3/api.py') diff --git a/README.rst b/README.rst index 4e5df72..214d30a 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,7 @@ +Fork +====================================== +Refactor and complete api wrapper. Intensive work in progress + Github3: Python wrapper for the (new) GitHub API v3 =================================================== @@ -75,4 +79,4 @@ Roadmap - Sphinx Documetnation - Examples - Unittests -- OAuth Last (how?) \ No newline at end of file +- OAuth Last (how?) diff --git a/github3/__init__.py b/github3/__init__.py index a99ea00..fcb882b 100644 --- a/github3/__init__.py +++ b/github3/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -#from core import * +from core import * diff --git a/github3/api.py b/github3/api.py index 6c41e53..2066e94 100644 --- a/github3/api.py +++ b/github3/api.py @@ -118,3 +118,6 @@ class GithubCore(object): error.process() return response + +class Github(GithubCore): + pass diff --git a/github3/config.py b/github3/config.py deleted file mode 100644 index 6fec55f..0000000 --- a/github3/config.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -github3.config -~~~~~~~~~~~~~~ - -This module provides the Github3 settings feature set. - -""" - -class Settings(object): - _singleton = {} - - # attributes with defaults - __attrs__ = [] - - def __init__(self, **kwargs): - super(Settings, self).__init__() - - self.__dict__ = self._singleton - - - def __call__(self, *args, **kwargs): - # new instance of class to call - r = self.__class__() - - # cache previous settings for __exit__ - r.__cache = self.__dict__.copy() - map(self.__cache.setdefault, self.__attrs__) - - # set new settings - self.__dict__.update(*args, **kwargs) - - return r - - - def __enter__(self): - pass - - - def __exit__(self, *args): - - # restore cached copy - self.__dict__.update(self.__cache.copy()) - del self.__cache - - - def __getattribute__(self, key): - if key in object.__getattribute__(self, '__attrs__'): - try: - return object.__getattribute__(self, key) - except AttributeError: - return None - return object.__getattribute__(self, key) - -settings = Settings() -settings.verbose = True -settings.base_url = 'https://api.github.com/' diff --git a/github3/core.py b/github3/core.py index 6221f9e..d6eed22 100644 --- a/github3/core.py +++ b/github3/core.py @@ -11,12 +11,7 @@ __version__ = '0.0.0' __license__ = 'MIT' __author__ = 'Kenneth Reitz' - -import envoy - -from .api import Github, settings - - +from .api import Github def no_auth(): """Returns an un-authenticated Github object.""" @@ -25,7 +20,6 @@ def no_auth(): return gh - def basic_auth(username, password): """Returns an authenticated Github object, via HTTP Basic.""" @@ -34,24 +28,3 @@ def basic_auth(username, password): gh.session.auth = (username, password) return gh - - - -# def git_config(): -# """Returns an authenticated Github object, via HTTP Basic. - -# GitHub API token is taken from `git config`. -# """ - -# username = envoy.run('git config github.user').std_out.strip() -# token = envoy.run('git config github.token').std_out.strip() - -# def enable_auth(*args, **kwargs): -# kwargs['auth'] = (username, token) -# return args, kwargs - -# gh = Github() -# gh.is_authenticated = True -# gh._requests_pre_hook = enable_auth - -# return gh \ No newline at end of file diff --git a/github3/handlers/__init__.py b/github3/handlers/__init__.py index 126efcb..e69de29 100644 --- a/github3/handlers/__init__.py +++ b/github3/handlers/__init__.py @@ -1 +0,0 @@ -from user import AuthUser, User diff --git a/github3/handlers/user.old.py b/github3/handlers/user.old.py new file mode 100644 index 0000000..5018aa1 --- /dev/null +++ b/github3/handlers/user.old.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina + +from .base import Handler +import github3.models as models +import github3.exceptions as exceptions + +class User(Handler): + """ Handler to query public user api """ + + def __init__(self, gh, username): + if not username: + raise exceptions.AnomUser("%s need a username" % self.__class__) + + self._url = ('users', username) + self._model = models.User + self.username = username + super(User, self).__init__(gh) + + def __repr__(self): + return ' %s' % self.username + + def get(self): + return self._get_resource() + + def get_followers(self, limit=None): + return self._get_resources('followers') + + def get_following(self, limit=None): + return self._get_resources('following') + + def get_repos(self, limit=None): + return self._get_resources('repos', model=models.Repo) + + def get_watched(self, limit=None): + return self._get_resources('watched', model=models.Repo) + + def get_orgs(self, limit=None): + return self._get_resources('orgs', model=models.Org) + + def get_gists(self, limit=None): + return self._get_resources('gists', model=models.Gist) + +class AuthUser(User): + """ Handler to query public/private api for authenticated user """ + + def __init__(self, gh): + self._url = ('user',) + self._model = models.User + super(User, self).__init__(gh) + + def __repr__(self): + return '' + + def get(self): + return self._get_resource(model=models.AuthUser) + + def get_emails(self): + return self._get_raw('emails') + + def get_is_following(self, user): + username = getattr(user, 'login', False) + if not username: + username = user + return self._get_bool('following', username) + + def get_keys(self): + return self._get_resources('keys', model=models.Key) + + def get_key(self, key_id): + return self._get_resource('keys', key_id, model=models.Key) + + def post_emails(self, *emails): + emails_parsed = map(str, emails) + return self._post_raw('emails', data=emails_parsed) + + def delete_emails(self, *emails): + emails_parsed = map(str, emails) + return self._delete_raw('emails', data=emails_parsed) diff --git a/github3/handlers/user.py b/github3/handlers/user.py deleted file mode 100644 index 5018aa1..0000000 --- a/github3/handlers/user.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -# -# author: David Medina - -from .base import Handler -import github3.models as models -import github3.exceptions as exceptions - -class User(Handler): - """ Handler to query public user api """ - - def __init__(self, gh, username): - if not username: - raise exceptions.AnomUser("%s need a username" % self.__class__) - - self._url = ('users', username) - self._model = models.User - self.username = username - super(User, self).__init__(gh) - - def __repr__(self): - return ' %s' % self.username - - def get(self): - return self._get_resource() - - def get_followers(self, limit=None): - return self._get_resources('followers') - - def get_following(self, limit=None): - return self._get_resources('following') - - def get_repos(self, limit=None): - return self._get_resources('repos', model=models.Repo) - - def get_watched(self, limit=None): - return self._get_resources('watched', model=models.Repo) - - def get_orgs(self, limit=None): - return self._get_resources('orgs', model=models.Org) - - def get_gists(self, limit=None): - return self._get_resources('gists', model=models.Gist) - -class AuthUser(User): - """ Handler to query public/private api for authenticated user """ - - def __init__(self, gh): - self._url = ('user',) - self._model = models.User - super(User, self).__init__(gh) - - def __repr__(self): - return '' - - def get(self): - return self._get_resource(model=models.AuthUser) - - def get_emails(self): - return self._get_raw('emails') - - def get_is_following(self, user): - username = getattr(user, 'login', False) - if not username: - username = user - return self._get_bool('following', username) - - def get_keys(self): - return self._get_resources('keys', model=models.Key) - - def get_key(self, key_id): - return self._get_resource('keys', key_id, model=models.Key) - - def post_emails(self, *emails): - emails_parsed = map(str, emails) - return self._post_raw('emails', data=emails_parsed) - - def delete_emails(self, *emails): - emails_parsed = map(str, emails) - return self._delete_raw('emails', data=emails_parsed) diff --git a/github3/packages/omnijson/__init__.py b/github3/packages/omnijson/__init__.py deleted file mode 100644 index c10c328..0000000 --- a/github3/packages/omnijson/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from .core import loads, dumps, JSONError - - -__all__ = ('loads', 'dumps', 'JSONError') - - -__version__ = '0.1.2' -__author__ = 'Kenneth Reitz' -__license__ = 'MIT' diff --git a/github3/packages/omnijson/core.py b/github3/packages/omnijson/core.py deleted file mode 100644 index 8b49537..0000000 --- a/github3/packages/omnijson/core.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -omijson.core -~~~~~~~~~~~~ - -This module provides the core omnijson functionality. - -""" - -import sys - -engine = None -_engine = None - - -options = [ - ['ujson', 'loads', 'dumps', (ValueError,)], - ['yajl', 'loads', 'dumps', (TypeError, ValueError)], - ['jsonlib2', 'read', 'write', (ValueError,)], - ['jsonlib', 'read', 'write', (ValueError,)], - ['simplejson', 'loads', 'dumps', (TypeError, ValueError)], - ['json', 'loads', 'dumps', (TypeError, ValueError)], - ['simplejson_from_packages', 'loads', 'dumps', (ValueError,)], -] - - -def _import(engine): - try: - if '_from_' in engine: - engine, package = engine.split('_from_') - m = __import__(package, globals(), locals(), [engine], -1) - return getattr(m, engine) - - return __import__(engine) - - except ImportError: - return False - - -def loads(s, **kwargs): - """Loads JSON object.""" - - try: - return _engine[0](s) - - except: - # crazy 2/3 exception hack - # http://www.voidspace.org.uk/python/weblog/arch_d7_2010_03_20.shtml - - ExceptionClass, why = sys.exc_info()[:2] - - if any([(issubclass(ExceptionClass, e)) for e in _engine[2]]): - raise JSONError(why) - else: - raise why - - -def dumps(o, **kwargs): - """Dumps JSON object.""" - - try: - return _engine[1](o) - - except: - ExceptionClass, why = sys.exc_info()[:2] - - if any([(issubclass(ExceptionClass, e)) for e in _engine[2]]): - raise JSONError(why) - else: - raise why - - -class JSONError(ValueError): - """JSON Failed.""" - - -# ------ -# Magic! -# ------ - - -for e in options: - - __engine = _import(e[0]) - - if __engine: - engine, _engine = e[0], e[1:4] - - for i in (0, 1): - _engine[i] = getattr(__engine, _engine[i]) - - break diff --git a/github3/packages/omnijson/packages/__init__.py b/github3/packages/omnijson/packages/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/github3/packages/omnijson/packages/simplejson/__init__.py b/github3/packages/omnijson/packages/simplejson/__init__.py deleted file mode 100644 index 210b957..0000000 --- a/github3/packages/omnijson/packages/simplejson/__init__.py +++ /dev/null @@ -1,438 +0,0 @@ -r"""JSON (JavaScript Object Notation) is a subset of -JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data -interchange format. - -:mod:`simplejson` exposes an API familiar to users of the standard library -:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained -version of the :mod:`json` library contained in Python 2.6, but maintains -compatibility with Python 2.4 and Python 2.5 and (currently) has -significant performance advantages, even without using the optional C -extension for speedups. - -Encoding basic Python object hierarchies:: - - >>> import simplejson as json - >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) - '["foo", {"bar": ["baz", null, 1.0, 2]}]' - >>> print json.dumps("\"foo\bar") - "\"foo\bar" - >>> print json.dumps(u'\u1234') - "\u1234" - >>> print json.dumps('\\') - "\\" - >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) - {"a": 0, "b": 0, "c": 0} - >>> from StringIO import StringIO - >>> io = StringIO() - >>> json.dump(['streaming API'], io) - >>> io.getvalue() - '["streaming API"]' - -Compact encoding:: - - >>> import simplejson as json - >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) - '[1,2,3,{"4":5,"6":7}]' - -Pretty printing:: - - >>> import simplejson as json - >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ') - >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) - { - "4": 5, - "6": 7 - } - -Decoding JSON:: - - >>> import simplejson as json - >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] - >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj - True - >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar' - True - >>> from StringIO import StringIO - >>> io = StringIO('["streaming API"]') - >>> json.load(io)[0] == 'streaming API' - True - -Specializing JSON object decoding:: - - >>> import simplejson as json - >>> def as_complex(dct): - ... if '__complex__' in dct: - ... return complex(dct['real'], dct['imag']) - ... return dct - ... - >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}', - ... object_hook=as_complex) - (1+2j) - >>> from decimal import Decimal - >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1') - True - -Specializing JSON object encoding:: - - >>> import simplejson as json - >>> def encode_complex(obj): - ... if isinstance(obj, complex): - ... return [obj.real, obj.imag] - ... raise TypeError(repr(o) + " is not JSON serializable") - ... - >>> json.dumps(2 + 1j, default=encode_complex) - '[2.0, 1.0]' - >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j) - '[2.0, 1.0]' - >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j)) - '[2.0, 1.0]' - - -Using simplejson.tool from the shell to validate and pretty-print:: - - $ echo '{"json":"obj"}' | python -m simplejson.tool - { - "json": "obj" - } - $ echo '{ 1.2:3.4}' | python -m simplejson.tool - Expecting property name: line 1 column 2 (char 2) -""" -__version__ = '2.1.6' -__all__ = [ - 'dump', 'dumps', 'load', 'loads', - 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', - 'OrderedDict', -] - -__author__ = 'Bob Ippolito ' - -from decimal import Decimal - -from decoder import JSONDecoder, JSONDecodeError -from encoder import JSONEncoder -def _import_OrderedDict(): - import collections - try: - return collections.OrderedDict - except AttributeError: - import ordered_dict - return ordered_dict.OrderedDict -OrderedDict = _import_OrderedDict() - -def _import_c_make_encoder(): - try: - from simplejson._speedups import make_encoder - return make_encoder - except ImportError: - return None - -_default_encoder = JSONEncoder( - skipkeys=False, - ensure_ascii=True, - check_circular=True, - allow_nan=True, - indent=None, - separators=None, - encoding='utf-8', - default=None, - use_decimal=False, -) - -def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, use_decimal=False, **kw): - """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a - ``.write()``-supporting file-like object). - - If ``skipkeys`` is true then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) - will be skipped instead of raising a ``TypeError``. - - If ``ensure_ascii`` is false, then the some chunks written to ``fp`` - may be ``unicode`` instances, subject to normal Python ``str`` to - ``unicode`` coercion rules. Unless ``fp.write()`` explicitly - understands ``unicode`` (as in ``codecs.getwriter()``) this is likely - to cause an error. - - If ``check_circular`` is false, then the circular reference check - for container types will be skipped and a circular reference will - result in an ``OverflowError`` (or worse). - - If ``allow_nan`` is false, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) - in strict compliance of the JSON specification, instead of using the - JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). - - If *indent* is a string, then JSON array elements and object members - will be pretty-printed with a newline followed by that string repeated - for each level of nesting. ``None`` (the default) selects the most compact - representation without any newlines. For backwards compatibility with - versions of simplejson earlier than 2.1.0, an integer is also accepted - and is converted to a string with that many spaces. - - If ``separators`` is an ``(item_separator, dict_separator)`` tuple - then it will be used instead of the default ``(', ', ': ')`` separators. - ``(',', ':')`` is the most compact JSON representation. - - ``encoding`` is the character encoding for str instances, default is UTF-8. - - ``default(obj)`` is a function that should return a serializable version - of obj or raise TypeError. The default simply raises TypeError. - - If *use_decimal* is true (default: ``False``) then decimal.Decimal - will be natively serialized to JSON with full precision. - - To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the - ``.default()`` method to serialize additional types), specify it with - the ``cls`` kwarg. - - """ - # cached encoder - if (not skipkeys and ensure_ascii and - check_circular and allow_nan and - cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not use_decimal - and not kw): - iterable = _default_encoder.iterencode(obj) - else: - if cls is None: - cls = JSONEncoder - iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, - check_circular=check_circular, allow_nan=allow_nan, indent=indent, - separators=separators, encoding=encoding, - default=default, use_decimal=use_decimal, **kw).iterencode(obj) - # could accelerate with writelines in some versions of Python, at - # a debuggability cost - for chunk in iterable: - fp.write(chunk) - - -def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, use_decimal=False, **kw): - """Serialize ``obj`` to a JSON formatted ``str``. - - If ``skipkeys`` is false then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) - will be skipped instead of raising a ``TypeError``. - - If ``ensure_ascii`` is false, then the return value will be a - ``unicode`` instance subject to normal Python ``str`` to ``unicode`` - coercion rules instead of being escaped to an ASCII ``str``. - - If ``check_circular`` is false, then the circular reference check - for container types will be skipped and a circular reference will - result in an ``OverflowError`` (or worse). - - If ``allow_nan`` is false, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in - strict compliance of the JSON specification, instead of using the - JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). - - If ``indent`` is a string, then JSON array elements and object members - will be pretty-printed with a newline followed by that string repeated - for each level of nesting. ``None`` (the default) selects the most compact - representation without any newlines. For backwards compatibility with - versions of simplejson earlier than 2.1.0, an integer is also accepted - and is converted to a string with that many spaces. - - If ``separators`` is an ``(item_separator, dict_separator)`` tuple - then it will be used instead of the default ``(', ', ': ')`` separators. - ``(',', ':')`` is the most compact JSON representation. - - ``encoding`` is the character encoding for str instances, default is UTF-8. - - ``default(obj)`` is a function that should return a serializable version - of obj or raise TypeError. The default simply raises TypeError. - - If *use_decimal* is true (default: ``False``) then decimal.Decimal - will be natively serialized to JSON with full precision. - - To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the - ``.default()`` method to serialize additional types), specify it with - the ``cls`` kwarg. - - """ - # cached encoder - if (not skipkeys and ensure_ascii and - check_circular and allow_nan and - cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not use_decimal - and not kw): - return _default_encoder.encode(obj) - if cls is None: - cls = JSONEncoder - return cls( - skipkeys=skipkeys, ensure_ascii=ensure_ascii, - check_circular=check_circular, allow_nan=allow_nan, indent=indent, - separators=separators, encoding=encoding, default=default, - use_decimal=use_decimal, **kw).encode(obj) - - -_default_decoder = JSONDecoder(encoding=None, object_hook=None, - object_pairs_hook=None) - - -def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, - parse_int=None, parse_constant=None, object_pairs_hook=None, - use_decimal=False, **kw): - """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing - a JSON document) to a Python object. - - *encoding* determines the encoding used to interpret any - :class:`str` objects decoded by this instance (``'utf-8'`` by - default). It has no effect when decoding :class:`unicode` objects. - - Note that currently only encodings that are a superset of ASCII work, - strings of other encodings should be passed in as :class:`unicode`. - - *object_hook*, if specified, will be called with the result of every - JSON object decoded and its return value will be used in place of the - given :class:`dict`. This can be used to provide custom - deserializations (e.g. to support JSON-RPC class hinting). - - *object_pairs_hook* is an optional function that will be called with - the result of any object literal decode with an ordered list of pairs. - The return value of *object_pairs_hook* will be used instead of the - :class:`dict`. This feature can be used to implement custom decoders - that rely on the order that the key and value pairs are decoded (for - example, :func:`collections.OrderedDict` will remember the order of - insertion). If *object_hook* is also defined, the *object_pairs_hook* - takes priority. - - *parse_float*, if specified, will be called with the string of every - JSON float to be decoded. By default, this is equivalent to - ``float(num_str)``. This can be used to use another datatype or parser - for JSON floats (e.g. :class:`decimal.Decimal`). - - *parse_int*, if specified, will be called with the string of every - JSON int to be decoded. By default, this is equivalent to - ``int(num_str)``. This can be used to use another datatype or parser - for JSON integers (e.g. :class:`float`). - - *parse_constant*, if specified, will be called with one of the - following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This - can be used to raise an exception if invalid JSON numbers are - encountered. - - If *use_decimal* is true (default: ``False``) then it implies - parse_float=decimal.Decimal for parity with ``dump``. - - To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` - kwarg. - - """ - return loads(fp.read(), - encoding=encoding, cls=cls, object_hook=object_hook, - parse_float=parse_float, parse_int=parse_int, - parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, - use_decimal=use_decimal, **kw) - - -def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, - parse_int=None, parse_constant=None, object_pairs_hook=None, - use_decimal=False, **kw): - """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON - document) to a Python object. - - *encoding* determines the encoding used to interpret any - :class:`str` objects decoded by this instance (``'utf-8'`` by - default). It has no effect when decoding :class:`unicode` objects. - - Note that currently only encodings that are a superset of ASCII work, - strings of other encodings should be passed in as :class:`unicode`. - - *object_hook*, if specified, will be called with the result of every - JSON object decoded and its return value will be used in place of the - given :class:`dict`. This can be used to provide custom - deserializations (e.g. to support JSON-RPC class hinting). - - *object_pairs_hook* is an optional function that will be called with - the result of any object literal decode with an ordered list of pairs. - The return value of *object_pairs_hook* will be used instead of the - :class:`dict`. This feature can be used to implement custom decoders - that rely on the order that the key and value pairs are decoded (for - example, :func:`collections.OrderedDict` will remember the order of - insertion). If *object_hook* is also defined, the *object_pairs_hook* - takes priority. - - *parse_float*, if specified, will be called with the string of every - JSON float to be decoded. By default, this is equivalent to - ``float(num_str)``. This can be used to use another datatype or parser - for JSON floats (e.g. :class:`decimal.Decimal`). - - *parse_int*, if specified, will be called with the string of every - JSON int to be decoded. By default, this is equivalent to - ``int(num_str)``. This can be used to use another datatype or parser - for JSON integers (e.g. :class:`float`). - - *parse_constant*, if specified, will be called with one of the - following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This - can be used to raise an exception if invalid JSON numbers are - encountered. - - If *use_decimal* is true (default: ``False``) then it implies - parse_float=decimal.Decimal for parity with ``dump``. - - To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` - kwarg. - - """ - if (cls is None and encoding is None and object_hook is None and - parse_int is None and parse_float is None and - parse_constant is None and object_pairs_hook is None - and not use_decimal and not kw): - return _default_decoder.decode(s) - if cls is None: - cls = JSONDecoder - if object_hook is not None: - kw['object_hook'] = object_hook - if object_pairs_hook is not None: - kw['object_pairs_hook'] = object_pairs_hook - if parse_float is not None: - kw['parse_float'] = parse_float - if parse_int is not None: - kw['parse_int'] = parse_int - if parse_constant is not None: - kw['parse_constant'] = parse_constant - if use_decimal: - if parse_float is not None: - raise TypeError("use_decimal=True implies parse_float=Decimal") - kw['parse_float'] = Decimal - return cls(encoding=encoding, **kw).decode(s) - - -def _toggle_speedups(enabled): - import simplejson.decoder as dec - import simplejson.encoder as enc - import simplejson.scanner as scan - c_make_encoder = _import_c_make_encoder() - if enabled: - dec.scanstring = dec.c_scanstring or dec.py_scanstring - enc.c_make_encoder = c_make_encoder - enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or - enc.py_encode_basestring_ascii) - scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner - else: - dec.scanstring = dec.py_scanstring - enc.c_make_encoder = None - enc.encode_basestring_ascii = enc.py_encode_basestring_ascii - scan.make_scanner = scan.py_make_scanner - dec.make_scanner = scan.make_scanner - global _default_decoder - _default_decoder = JSONDecoder( - encoding=None, - object_hook=None, - object_pairs_hook=None, - ) - global _default_encoder - _default_encoder = JSONEncoder( - skipkeys=False, - ensure_ascii=True, - check_circular=True, - allow_nan=True, - indent=None, - separators=None, - encoding='utf-8', - default=None, - ) diff --git a/github3/packages/omnijson/packages/simplejson/decoder.py b/github3/packages/omnijson/packages/simplejson/decoder.py deleted file mode 100644 index 3e36e56..0000000 --- a/github3/packages/omnijson/packages/simplejson/decoder.py +++ /dev/null @@ -1,421 +0,0 @@ -"""Implementation of JSONDecoder -""" -import re -import sys -import struct - -from .scanner import make_scanner -def _import_c_scanstring(): - try: - from simplejson._speedups import scanstring - return scanstring - except ImportError: - return None -c_scanstring = _import_c_scanstring() - -__all__ = ['JSONDecoder'] - -FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL - -def _floatconstants(): - _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') - # The struct module in Python 2.4 would get frexp() out of range here - # when an endian is specified in the format string. Fixed in Python 2.5+ - if sys.byteorder != 'big': - _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] - nan, inf = struct.unpack('dd', _BYTES) - return nan, inf, -inf - -NaN, PosInf, NegInf = _floatconstants() - - -class JSONDecodeError(ValueError): - """Subclass of ValueError with the following additional properties: - - msg: The unformatted error message - doc: The JSON document being parsed - pos: The start index of doc where parsing failed - end: The end index of doc where parsing failed (may be None) - lineno: The line corresponding to pos - colno: The column corresponding to pos - endlineno: The line corresponding to end (may be None) - endcolno: The column corresponding to end (may be None) - - """ - def __init__(self, msg, doc, pos, end=None): - ValueError.__init__(self, errmsg(msg, doc, pos, end=end)) - self.msg = msg - self.doc = doc - self.pos = pos - self.end = end - self.lineno, self.colno = linecol(doc, pos) - if end is not None: - self.endlineno, self.endcolno = linecol(doc, end) - else: - self.endlineno, self.endcolno = None, None - - -def linecol(doc, pos): - lineno = doc.count('\n', 0, pos) + 1 - if lineno == 1: - colno = pos - else: - colno = pos - doc.rindex('\n', 0, pos) - return lineno, colno - - -def errmsg(msg, doc, pos, end=None): - # Note that this function is called from _speedups - lineno, colno = linecol(doc, pos) - if end is None: - #fmt = '{0}: line {1} column {2} (char {3})' - #return fmt.format(msg, lineno, colno, pos) - fmt = '%s: line %d column %d (char %d)' - return fmt % (msg, lineno, colno, pos) - endlineno, endcolno = linecol(doc, end) - #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})' - #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end) - fmt = '%s: line %d column %d - line %d column %d (char %d - %d)' - return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end) - - -_CONSTANTS = { - '-Infinity': NegInf, - 'Infinity': PosInf, - 'NaN': NaN, -} - -STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) -BACKSLASH = { - '"': u'"', '\\': u'\\', '/': u'/', - 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', -} - -DEFAULT_ENCODING = "utf-8" - -def py_scanstring(s, end, encoding=None, strict=True, - _b=BACKSLASH, _m=STRINGCHUNK.match): - """Scan the string s for a JSON string. End is the index of the - character in s after the quote that started the JSON string. - Unescapes all valid JSON string escape sequences and raises ValueError - on attempt to decode an invalid string. If strict is False then literal - control characters are allowed in the string. - - Returns a tuple of the decoded string and the index of the character in s - after the end quote.""" - if encoding is None: - encoding = DEFAULT_ENCODING - chunks = [] - _append = chunks.append - begin = end - 1 - while 1: - chunk = _m(s, end) - if chunk is None: - raise JSONDecodeError( - "Unterminated string starting at", s, begin) - end = chunk.end() - content, terminator = chunk.groups() - # Content is contains zero or more unescaped string characters - if content: - if not isinstance(content, unicode): - content = unicode(content, encoding) - _append(content) - # Terminator is the end of string, a literal control character, - # or a backslash denoting that an escape sequence follows - if terminator == '"': - break - elif terminator != '\\': - if strict: - msg = "Invalid control character %r at" % (terminator,) - #msg = "Invalid control character {0!r} at".format(terminator) - raise JSONDecodeError(msg, s, end) - else: - _append(terminator) - continue - try: - esc = s[end] - except IndexError: - raise JSONDecodeError( - "Unterminated string starting at", s, begin) - # If not a unicode escape sequence, must be in the lookup table - if esc != 'u': - try: - char = _b[esc] - except KeyError: - msg = "Invalid \\escape: " + repr(esc) - raise JSONDecodeError(msg, s, end) - end += 1 - else: - # Unicode escape sequence - esc = s[end + 1:end + 5] - next_end = end + 5 - if len(esc) != 4: - msg = "Invalid \\uXXXX escape" - raise JSONDecodeError(msg, s, end) - uni = int(esc, 16) - # Check for surrogate pair on UCS-4 systems - if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: - msg = "Invalid \\uXXXX\\uXXXX surrogate pair" - if not s[end + 5:end + 7] == '\\u': - raise JSONDecodeError(msg, s, end) - esc2 = s[end + 7:end + 11] - if len(esc2) != 4: - raise JSONDecodeError(msg, s, end) - uni2 = int(esc2, 16) - uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) - next_end += 6 - char = unichr(uni) - end = next_end - # Append the unescaped character - _append(char) - return u''.join(chunks), end - - -# Use speedup if available -scanstring = c_scanstring or py_scanstring - -WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) -WHITESPACE_STR = ' \t\n\r' - -def JSONObject((s, end), encoding, strict, scan_once, object_hook, - object_pairs_hook, memo=None, - _w=WHITESPACE.match, _ws=WHITESPACE_STR): - # Backwards compatibility - if memo is None: - memo = {} - memo_get = memo.setdefault - pairs = [] - # Use a slice to prevent IndexError from being raised, the following - # check will raise a more specific ValueError if the string is empty - nextchar = s[end:end + 1] - # Normally we expect nextchar == '"' - if nextchar != '"': - if nextchar in _ws: - end = _w(s, end).end() - nextchar = s[end:end + 1] - # Trivial empty object - if nextchar == '}': - if object_pairs_hook is not None: - result = object_pairs_hook(pairs) - return result, end + 1 - pairs = {} - if object_hook is not None: - pairs = object_hook(pairs) - return pairs, end + 1 - elif nextchar != '"': - raise JSONDecodeError("Expecting property name", s, end) - end += 1 - while True: - key, end = scanstring(s, end, encoding, strict) - key = memo_get(key, key) - - # To skip some function call overhead we optimize the fast paths where - # the JSON key separator is ": " or just ":". - if s[end:end + 1] != ':': - end = _w(s, end).end() - if s[end:end + 1] != ':': - raise JSONDecodeError("Expecting : delimiter", s, end) - - end += 1 - - try: - if s[end] in _ws: - end += 1 - if s[end] in _ws: - end = _w(s, end + 1).end() - except IndexError: - pass - - try: - value, end = scan_once(s, end) - except StopIteration: - raise JSONDecodeError("Expecting object", s, end) - pairs.append((key, value)) - - try: - nextchar = s[end] - if nextchar in _ws: - end = _w(s, end + 1).end() - nextchar = s[end] - except IndexError: - nextchar = '' - end += 1 - - if nextchar == '}': - break - elif nextchar != ',': - raise JSONDecodeError("Expecting , delimiter", s, end - 1) - - try: - nextchar = s[end] - if nextchar in _ws: - end += 1 - nextchar = s[end] - if nextchar in _ws: - end = _w(s, end + 1).end() - nextchar = s[end] - except IndexError: - nextchar = '' - - end += 1 - if nextchar != '"': - raise JSONDecodeError("Expecting property name", s, end - 1) - - if object_pairs_hook is not None: - result = object_pairs_hook(pairs) - return result, end - pairs = dict(pairs) - if object_hook is not None: - pairs = object_hook(pairs) - return pairs, end - -def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): - values = [] - nextchar = s[end:end + 1] - if nextchar in _ws: - end = _w(s, end + 1).end() - nextchar = s[end:end + 1] - # Look-ahead for trivial empty array - if nextchar == ']': - return values, end + 1 - _append = values.append - while True: - try: - value, end = scan_once(s, end) - except StopIteration: - raise JSONDecodeError("Expecting object", s, end) - _append(value) - nextchar = s[end:end + 1] - if nextchar in _ws: - end = _w(s, end + 1).end() - nextchar = s[end:end + 1] - end += 1 - if nextchar == ']': - break - elif nextchar != ',': - raise JSONDecodeError("Expecting , delimiter", s, end) - - try: - if s[end] in _ws: - end += 1 - if s[end] in _ws: - end = _w(s, end + 1).end() - except IndexError: - pass - - return values, end - -class JSONDecoder(object): - """Simple JSON decoder - - Performs the following translations in decoding by default: - - +---------------+-------------------+ - | JSON | Python | - +===============+===================+ - | object | dict | - +---------------+-------------------+ - | array | list | - +---------------+-------------------+ - | string | unicode | - +---------------+-------------------+ - | number (int) | int, long | - +---------------+-------------------+ - | number (real) | float | - +---------------+-------------------+ - | true | True | - +---------------+-------------------+ - | false | False | - +---------------+-------------------+ - | null | None | - +---------------+-------------------+ - - It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as - their corresponding ``float`` values, which is outside the JSON spec. - - """ - - def __init__(self, encoding=None, object_hook=None, parse_float=None, - parse_int=None, parse_constant=None, strict=True, - object_pairs_hook=None): - """ - *encoding* determines the encoding used to interpret any - :class:`str` objects decoded by this instance (``'utf-8'`` by - default). It has no effect when decoding :class:`unicode` objects. - - Note that currently only encodings that are a superset of ASCII work, - strings of other encodings should be passed in as :class:`unicode`. - - *object_hook*, if specified, will be called with the result of every - JSON object decoded and its return value will be used in place of the - given :class:`dict`. This can be used to provide custom - deserializations (e.g. to support JSON-RPC class hinting). - - *object_pairs_hook* is an optional function that will be called with - the result of any object literal decode with an ordered list of pairs. - The return value of *object_pairs_hook* will be used instead of the - :class:`dict`. This feature can be used to implement custom decoders - that rely on the order that the key and value pairs are decoded (for - example, :func:`collections.OrderedDict` will remember the order of - insertion). If *object_hook* is also defined, the *object_pairs_hook* - takes priority. - - *parse_float*, if specified, will be called with the string of every - JSON float to be decoded. By default, this is equivalent to - ``float(num_str)``. This can be used to use another datatype or parser - for JSON floats (e.g. :class:`decimal.Decimal`). - - *parse_int*, if specified, will be called with the string of every - JSON int to be decoded. By default, this is equivalent to - ``int(num_str)``. This can be used to use another datatype or parser - for JSON integers (e.g. :class:`float`). - - *parse_constant*, if specified, will be called with one of the - following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This - can be used to raise an exception if invalid JSON numbers are - encountered. - - *strict* controls the parser's behavior when it encounters an - invalid control character in a string. The default setting of - ``True`` means that unescaped control characters are parse errors, if - ``False`` then control characters will be allowed in strings. - - """ - self.encoding = encoding - self.object_hook = object_hook - self.object_pairs_hook = object_pairs_hook - self.parse_float = parse_float or float - self.parse_int = parse_int or int - self.parse_constant = parse_constant or _CONSTANTS.__getitem__ - self.strict = strict - self.parse_object = JSONObject - self.parse_array = JSONArray - self.parse_string = scanstring - self.memo = {} - self.scan_once = make_scanner(self) - - def decode(self, s, _w=WHITESPACE.match): - """Return the Python representation of ``s`` (a ``str`` or ``unicode`` - instance containing a JSON document) - - """ - obj, end = self.raw_decode(s, idx=_w(s, 0).end()) - end = _w(s, end).end() - if end != len(s): - raise JSONDecodeError("Extra data", s, end, len(s)) - return obj - - def raw_decode(self, s, idx=0): - """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` - beginning with a JSON document) and return a 2-tuple of the Python - representation and the index in ``s`` where the document ended. - - This can be used to decode a JSON document from a string that may - have extraneous data at the end. - - """ - try: - obj, end = self.scan_once(s, idx) - except StopIteration: - raise JSONDecodeError("No JSON object could be decoded", s, idx) - return obj, end diff --git a/github3/packages/omnijson/packages/simplejson/encoder.py b/github3/packages/omnijson/packages/simplejson/encoder.py deleted file mode 100644 index f1269f3..0000000 --- a/github3/packages/omnijson/packages/simplejson/encoder.py +++ /dev/null @@ -1,503 +0,0 @@ -"""Implementation of JSONEncoder -""" -import re -from decimal import Decimal - -def _import_speedups(): - try: - from simplejson import _speedups - return _speedups.encode_basestring_ascii, _speedups.make_encoder - except ImportError: - return None, None -c_encode_basestring_ascii, c_make_encoder = _import_speedups() - -from .decoder import PosInf - -ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') -ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') -HAS_UTF8 = re.compile(r'[\x80-\xff]') -ESCAPE_DCT = { - '\\': '\\\\', - '"': '\\"', - '\b': '\\b', - '\f': '\\f', - '\n': '\\n', - '\r': '\\r', - '\t': '\\t', -} -for i in range(0x20): - #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i)) - ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) - -FLOAT_REPR = repr - -def encode_basestring(s): - """Return a JSON representation of a Python string - - """ - if isinstance(s, str) and HAS_UTF8.search(s) is not None: - s = s.decode('utf-8') - def replace(match): - return ESCAPE_DCT[match.group(0)] - return u'"' + ESCAPE.sub(replace, s) + u'"' - - -def py_encode_basestring_ascii(s): - """Return an ASCII-only JSON representation of a Python string - - """ - if isinstance(s, str) and HAS_UTF8.search(s) is not None: - s = s.decode('utf-8') - def replace(match): - s = match.group(0) - try: - return ESCAPE_DCT[s] - except KeyError: - n = ord(s) - if n < 0x10000: - #return '\\u{0:04x}'.format(n) - return '\\u%04x' % (n,) - else: - # surrogate pair - n -= 0x10000 - s1 = 0xd800 | ((n >> 10) & 0x3ff) - s2 = 0xdc00 | (n & 0x3ff) - #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2) - return '\\u%04x\\u%04x' % (s1, s2) - return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' - - -encode_basestring_ascii = ( - c_encode_basestring_ascii or py_encode_basestring_ascii) - -class JSONEncoder(object): - """Extensible JSON encoder for Python data structures. - - Supports the following objects and types by default: - - +-------------------+---------------+ - | Python | JSON | - +===================+===============+ - | dict | object | - +-------------------+---------------+ - | list, tuple | array | - +-------------------+---------------+ - | str, unicode | string | - +-------------------+---------------+ - | int, long, float | number | - +-------------------+---------------+ - | True | true | - +-------------------+---------------+ - | False | false | - +-------------------+---------------+ - | None | null | - +-------------------+---------------+ - - To extend this to recognize other objects, subclass and implement a - ``.default()`` method with another method that returns a serializable - object for ``o`` if possible, otherwise it should call the superclass - implementation (to raise ``TypeError``). - - """ - item_separator = ', ' - key_separator = ': ' - def __init__(self, skipkeys=False, ensure_ascii=True, - check_circular=True, allow_nan=True, sort_keys=False, - indent=None, separators=None, encoding='utf-8', default=None, - use_decimal=False): - """Constructor for JSONEncoder, with sensible defaults. - - If skipkeys is false, then it is a TypeError to attempt - encoding of keys that are not str, int, long, float or None. If - skipkeys is True, such items are simply skipped. - - If ensure_ascii is true, the output is guaranteed to be str - objects with all incoming unicode characters escaped. If - ensure_ascii is false, the output will be unicode object. - - If check_circular is true, then lists, dicts, and custom encoded - objects will be checked for circular references during encoding to - prevent an infinite recursion (which would cause an OverflowError). - Otherwise, no such check takes place. - - If allow_nan is true, then NaN, Infinity, and -Infinity will be - encoded as such. This behavior is not JSON specification compliant, - but is consistent with most JavaScript based encoders and decoders. - Otherwise, it will be a ValueError to encode such floats. - - If sort_keys is true, then the output of dictionaries will be - sorted by key; this is useful for regression tests to ensure - that JSON serializations can be compared on a day-to-day basis. - - If indent is a string, then JSON array elements and object members - will be pretty-printed with a newline followed by that string repeated - for each level of nesting. ``None`` (the default) selects the most compact - representation without any newlines. For backwards compatibility with - versions of simplejson earlier than 2.1.0, an integer is also accepted - and is converted to a string with that many spaces. - - If specified, separators should be a (item_separator, key_separator) - tuple. The default is (', ', ': '). To get the most compact JSON - representation you should specify (',', ':') to eliminate whitespace. - - If specified, default is a function that gets called for objects - that can't otherwise be serialized. It should return a JSON encodable - version of the object or raise a ``TypeError``. - - If encoding is not None, then all input strings will be - transformed into unicode using that encoding prior to JSON-encoding. - The default is UTF-8. - - If use_decimal is true (not the default), ``decimal.Decimal`` will - be supported directly by the encoder. For the inverse, decode JSON - with ``parse_float=decimal.Decimal``. - - """ - - self.skipkeys = skipkeys - self.ensure_ascii = ensure_ascii - self.check_circular = check_circular - self.allow_nan = allow_nan - self.sort_keys = sort_keys - self.use_decimal = use_decimal - if isinstance(indent, (int, long)): - indent = ' ' * indent - self.indent = indent - if separators is not None: - self.item_separator, self.key_separator = separators - elif indent is not None: - self.item_separator = ',' - if default is not None: - self.default = default - self.encoding = encoding - - def default(self, o): - """Implement this method in a subclass such that it returns - a serializable object for ``o``, or calls the base implementation - (to raise a ``TypeError``). - - For example, to support arbitrary iterators, you could - implement default like this:: - - def default(self, o): - try: - iterable = iter(o) - except TypeError: - pass - else: - return list(iterable) - return JSONEncoder.default(self, o) - - """ - raise TypeError(repr(o) + " is not JSON serializable") - - def encode(self, o): - """Return a JSON string representation of a Python data structure. - - >>> from simplejson import JSONEncoder - >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) - '{"foo": ["bar", "baz"]}' - - """ - # This is for extremely simple cases and benchmarks. - if isinstance(o, basestring): - if isinstance(o, str): - _encoding = self.encoding - if (_encoding is not None - and not (_encoding == 'utf-8')): - o = o.decode(_encoding) - if self.ensure_ascii: - return encode_basestring_ascii(o) - else: - return encode_basestring(o) - # This doesn't pass the iterator directly to ''.join() because the - # exceptions aren't as detailed. The list call should be roughly - # equivalent to the PySequence_Fast that ''.join() would do. - chunks = self.iterencode(o, _one_shot=True) - if not isinstance(chunks, (list, tuple)): - chunks = list(chunks) - if self.ensure_ascii: - return ''.join(chunks) - else: - return u''.join(chunks) - - def iterencode(self, o, _one_shot=False): - """Encode the given object and yield each string - representation as available. - - For example:: - - for chunk in JSONEncoder().iterencode(bigobject): - mysocket.write(chunk) - - """ - if self.check_circular: - markers = {} - else: - markers = None - if self.ensure_ascii: - _encoder = encode_basestring_ascii - else: - _encoder = encode_basestring - if self.encoding != 'utf-8': - def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): - if isinstance(o, str): - o = o.decode(_encoding) - return _orig_encoder(o) - - def floatstr(o, allow_nan=self.allow_nan, - _repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf): - # Check for specials. Note that this type of test is processor - # and/or platform-specific, so do tests which don't depend on - # the internals. - - if o != o: - text = 'NaN' - elif o == _inf: - text = 'Infinity' - elif o == _neginf: - text = '-Infinity' - else: - return _repr(o) - - if not allow_nan: - raise ValueError( - "Out of range float values are not JSON compliant: " + - repr(o)) - - return text - - - key_memo = {} - if (_one_shot and c_make_encoder is not None - and self.indent is None): - _iterencode = c_make_encoder( - markers, self.default, _encoder, self.indent, - self.key_separator, self.item_separator, self.sort_keys, - self.skipkeys, self.allow_nan, key_memo, self.use_decimal) - else: - _iterencode = _make_iterencode( - markers, self.default, _encoder, self.indent, floatstr, - self.key_separator, self.item_separator, self.sort_keys, - self.skipkeys, _one_shot, self.use_decimal) - try: - return _iterencode(o, 0) - finally: - key_memo.clear() - - -class JSONEncoderForHTML(JSONEncoder): - """An encoder that produces JSON safe to embed in HTML. - - To embed JSON content in, say, a script tag on a web page, the - characters &, < and > should be escaped. They cannot be escaped - with the usual entities (e.g. &) because they are not expanded - within