diff options
-rw-r--r-- | github3/__init__.py | 2 | ||||
-rw-r--r-- | github3/core.py | 124 | ||||
-rw-r--r-- | github3/handlers/base.py | 62 | ||||
-rw-r--r-- | github3/models/base.py | 145 | ||||
-rw-r--r-- | github3/models/gists.py | 68 | ||||
-rw-r--r-- | github3/models/orgs.py | 27 | ||||
-rw-r--r-- | github3/models/repos.py | 35 | ||||
-rw-r--r-- | github3/models/user.py | 69 |
8 files changed, 236 insertions, 296 deletions
diff --git a/github3/__init__.py b/github3/__init__.py index fcb882b..40a96af 100644 --- a/github3/__init__.py +++ b/github3/__init__.py @@ -1,3 +1 @@ # -*- coding: utf-8 -*- - -from core import * diff --git a/github3/core.py b/github3/core.py index d6eed22..0aa5949 100644 --- a/github3/core.py +++ b/github3/core.py @@ -1,30 +1,114 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# +# author: David Medina -""" -github3.core -~~~~~~~~~~~~ +class Paginate: + """ Paginate resource iterator -This module provides the entrance point for the GitHub3 module. -""" + :param resource: URL resource + :param requester: Bound method to request. See `GithubCore.get` + """ -__version__ = '0.0.0' -__license__ = 'MIT' -__author__ = 'Kenneth Reitz' + def __init__(self, resource, requester): + self.resource = resource + self.requester = requester + self.page = 1 -from .api import Github + 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 + for link, rels in parse_link_value(link).items(): + if rels.get('rel') == 'last': + query = urlparse(link).query + self.last = int(parse_qs(query).get('page').pop()) -def no_auth(): - """Returns an un-authenticated Github object.""" + return self.last - gh = Github() + def __iter__(self): + return self - return gh + 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 -def basic_auth(username, password): - """Returns an authenticated Github object, via HTTP Basic.""" + def next(self): + if self.page == 1: + content = self.initial() + self.page += 1 + return content + else: + if self.page > self.last: + raise StopIteration + else: + content = self.requester(self.resource, page=self.page) + self.page += 1 + return content - gh = Github() - gh.is_authenticated = True - gh.session.auth = (username, password) +class Modelizer(object): + """ Converter json into model and vice versa """ - return gh + def __init__(self, model): + self.model = model + self.attrs = {} + + def _parse_date(self, string_date): + from datetime import datetime + try: + date = datetime.strptime(string_date, '%Y-%m-%dT%H:%M:%SZ') + except TypeError: + date = None + + return date + + def _parse_map(self, model, raw_resource): + if model == 'self': + model = self.model + + return Modelizer(model).loads(raw_resource) + + def _parse_collection_map(self, model, raw_resources): + # Dict of resources (Ex: Gist file) + if getattr(raw_resources, 'items', False): + dict_map = {} + for key, raw_resource in raw_resources.items(): + dict_map[key] = Modelizer(model).loads(raw_resource) + return dict_map + # list of resources + else: + return [Modelizer(model).loads(raw_resource) + for raw_resource in raw_resources] + + def loads(self, raw_resource): + attrs = {} + idl = self.model.idl() + attrs.update( + {attr: raw_resource[attr] for attr in idl.get('strs',()) + if raw_resource.get(attr)}) + attrs.update( + {attr: raw_resource[attr] for attr in idl.get('ints',()) + if raw_resource.get(attr)}) + attrs.update( + {attr: self._parse_date(raw_resource[attr]) + for attr in idl.get('dates',()) if raw_resource.get(attr)}) + attrs.update( + {attr: raw_resource[attr] for attr in idl.get('bools',()) + if raw_resource.get(attr)}) + attrs.update( + {attr: self._parse_map(model, raw_resource[attr]) + for attr, model in idl.get('maps',{}).items() + if raw_resource.get(attr)}) + attrs.update( + {attr: self._parse_collection_map(model, raw_resource[attr]) + for attr, model in idl.get('collection_maps',{}).items() + if raw_resource.get(attr)}) + + return self.model(attrs) + + def dumps(self): + # return JSON + pass diff --git a/github3/handlers/base.py b/github3/handlers/base.py index 3f82817..0d8be0e 100644 --- a/github3/handlers/base.py +++ b/github3/handlers/base.py @@ -3,53 +3,7 @@ # # author: David Medina -import github3.exceptions as ghexceptions - -class Paginate: - """ Paginate resource iterator - - :param resource: URL resource - :param requester: Bound method to request. See `GithubCore.get` - """ - - def __init__(self, resource, requester): - self.resource = resource - self.requester = requester - 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 - for link, rels in parse_link_value(link).items(): - if rels.get('rel') == 'last': - query = urlparse(link).query - self.last = int(parse_qs(query).get('page').pop()) - - return self.last - - def __iter__(self): - 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 - - def next(self): - if self.page == 1: - content = self.initial() - self.page += 1 - return content - else: - if self.page > self.last: - raise StopIteration - else: - content = self.requester(self.resource, page=self.page) - self.page += 1 - return content +from github3.core import Paginate, Modelizer class Handler(object): """ Handler base. Requests to API and modelize responses """ @@ -60,9 +14,11 @@ class Handler(object): def _bool(self, resource, **kwargs): """ Handler request to boolean response """ + + from github3.exceptions import NotFound try: response = self._gh.head(resource, **kwargs) - except ghexceptions.NotFound: + except NotFound: return False assert response.status_code == 204 return True @@ -70,13 +26,21 @@ class Handler(object): #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: for raw_resource in page: if limit and counter > limit: break counter += 1 - yield raw_resource + yield Modelizer(model or self.model).loads(raw_resource) + #yield raw_resource else: continue break + + def _get_resource(self, resource, model=None): + """ Handler request to single request """ + + raw_resource = self._gh.get(resource) + return Modelizer(model or self.model).loads(raw_resource) diff --git a/github3/models/base.py b/github3/models/base.py index f98af2d..df0c82b 100644 --- a/github3/models/base.py +++ b/github3/models/base.py @@ -2,149 +2,18 @@ github3.models ~~~~~~~~~~~~~~ -This module provides the Github3 object model. +This package provides the Github3 object model. """ -import json -import inspect - -from github3.helpers import to_python, to_api, key_diff - class BaseResource(object): """A BaseResource object.""" - _strs = [] - _ints = [] - _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[1]), - inspect.getmembers(handler, inspect.ismethod)) - for name, callback in methods: - setattr(self, name, callback) - except: - pass - - def __init__(self): - self._bootstrap() + def __init__(self, attrs=None): + if attrs: + for attr, value in attrs.items(): + setattr(self, attr, value) super(BaseResource, self).__init__() - def __dir__(self): - return self.keys() - - def _bootstrap(self): - """Bootstraps the model object based on configured values.""" - - for attr in self.keys(): - setattr(self, attr, None) - - def keys(self): - return self._strs + self._ints + self._dates + self._bools + self._map.keys() - - def dict(self): - d = dict() - for k in self.keys(): - d[k] = self.__dict__.get(k) - - return d - @classmethod - def new_from_dict(cls, d, gh=None): - - return to_python( - obj=cls(), in_dict=d, - str_keys = cls._strs, - int_keys = cls._ints, - date_keys = cls._dates, - bool_keys = cls._bools, - object_map = cls._map, - list_map = cls._list_map, - _gh = gh - ) - - - def update(self): - deploy = key_diff(self._cache, self.dict(), pack=True) - - deploy = to_api(deploy, int_keys=self._ints, date_keys=self._dates, bool_keys=self._bools) - deploy = json.dumps(deploy) - - r = self._gh._patch_resource(self.ri, deploy) - return r - - - -#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 '<org {0}>'.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) -# -# + def idl(self): + raise NotImplementedError('Each model need subcass that method') diff --git a/github3/models/gists.py b/github3/models/gists.py index 5ad61c3..d1b416d 100644 --- a/github3/models/gists.py +++ b/github3/models/gists.py @@ -7,43 +7,71 @@ from .base import BaseResource from .user import User class File(BaseResource): - _strs = ['filename', 'raw_url', 'content', 'language', 'type'] - _ints = ['size'] + """ File model """ + + @classmethod + def idl(self): + return { + 'strs': ['filename', 'raw_url', 'content', 'language', 'type'], + 'ints': ['size'], + } def __repr__(self): return '<File gist> %s' % self.filename class GistFork(BaseResource): - _strs = ['url'] - _dates = ['created_at'] - _map = {'user': User} + """ GistFork model """ + + @classmethod + def idl(self): + return { + 'strs': ['url'], + 'dates': ['created_at'], + 'maps': {'user': User} + } def __repr__(self): return '<Gist fork> %s>' % self.user.login class ChangeStatus(BaseResource): - _ints = ['deletions', 'additions', 'total'] + """ ChangeStatus model """ + + @classmethod + def idl(self): + return { + 'ints': ['deletions', 'additions', 'total'], + } def __repr__(self): return '<Gist history> change_status>' class GistHistory(BaseResource): - _strs = ['url', 'version'] - _map = {'user': User, 'change_status': ChangeStatus} - _dates = ['committed_at'] + """ """ + + @classmethod + def idl(self): + return { + 'strs': ['url', 'version'], + 'maps': {'user': User, 'change_status': ChangeStatus}, + 'dates': ['committed_at'], + } + + def __repr__(self): + return '<GistHistory %s/%s>' % (self.user, self.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) + @classmethod + def idl(self): + return { + 'strs': ['url', 'description', 'html_url', 'git_pull_url', 'git_push_url'], + 'ints': ['id', 'comments'], + 'bools': ['public'], + 'dates': ['created_at'], + 'maps': {'user': User}, + 'collection_maps': {'files': File, 'forks': GistFork, 'history': GistHistory}, + } def __repr__(self): - return '<gist %s/%s>' % (self.user.login, self.description) - + return '<Gist %s/%s>' % (self.user, self.description) diff --git a/github3/models/orgs.py b/github3/models/orgs.py index 1ce638e..840b51a 100644 --- a/github3/models/orgs.py +++ b/github3/models/orgs.py @@ -9,20 +9,17 @@ 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) + @classmethod + def idl(self): + return { + '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'], + 'maps': {'plan': plan} + } def __repr__(self): - return '<org {0}>'.format(self.login) + return '<Org %s>' % self.login diff --git a/github3/models/repos.py b/github3/models/repos.py index 8dbe970..ba6ac33 100644 --- a/github3/models/repos.py +++ b/github3/models/repos.py @@ -8,23 +8,24 @@ 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', - } + """ Repo model """ - @property - def ri(self): - return ('repos', self.owner.login, self.name) + @classmethod + def idl(self): + return { + 'strs': [ + 'url', 'html_url', 'clone_url', 'git_url', 'ssh_url', 'svn_url', + 'name', 'description', 'homepage', 'language', 'master_branch'], + 'ints': ['forks', 'watchers', 'size', 'open_issues'], + 'dates': ['created_at', 'pushed_at'], + 'bools': ['private', 'fork', 'has_issues', 'has_wiki', 'has_downloads'], + 'maps': { + 'owner': User, + 'organization': Org, + 'parent': 'self', + 'source': 'self', + } + } def __repr__(self): - return '<Repo {0}/{1}>'.format(self.owner.login, self.name) - # owner + return '<Repo %s/%s>' % (self.owner.login, self.name) diff --git a/github3/models/user.py b/github3/models/user.py index 3f0efc4..7ec7999 100644 --- a/github3/models/user.py +++ b/github3/models/user.py @@ -8,59 +8,58 @@ from .base import BaseResource class Plan(BaseResource): """Github Plan object model.""" - _strs = ['name'] - _ints = ['space', 'collaborators', 'private_repos'] + @classmethod + def idl(self): + return { + 'strs': ['name'], + 'ints': ['space', 'collaborators', 'private_repos'], + } def __repr__(self): - return '<Plan {0}>'.format(str(self.name)) + return '<Plan %s>' % self.name class Key(BaseResource): """Github Key object model.""" - _strs = ['url', 'title', 'key'] - _ints = ['id'] + @classmethod + def idl(self): + return { + 'strs': ['url', 'title', 'key'], + 'ints': ['id'], + } def __repr__(self): - return '<Key {0}>'.format(str(self.title)) + return '<Key %s>' % self.title class User(BaseResource): """Github User object model.""" - _strs = [ - 'login','avatar_url', 'url', 'name', 'company', 'blog', 'location', - 'email', 'bio', 'html_url', 'type'] - - _ints = ['id', 'public_repos', 'public_gists', 'followers', 'following'] - _dates = ['created_at',] - _bools = ['hireable', ] - - @property - def ri(self): - return ('users', self.login) + @classmethod + def idl(self): + return { + 'strs': ['login','avatar_url', 'url', 'name', 'company', 'blog', + 'location', 'email', 'bio', 'html_url', 'type'], + 'ints': [ + 'id', 'public_repos', 'public_gists', 'followers', 'following', + 'total_private_repos', 'owned_private_repos', 'private_gists', + 'disk_usage', 'collaborators'], + 'maps': {'plan': Plan}, + 'dates': ['created_at',], + 'bools': ['hireable', ], + } def __repr__(self): - return '<model.User {0}>'.format(self.login) + return '<User %s>' % self.login - def handler(self): - return self._gh.user_handler(self.login, force=True) + #def handler(self): + # return self._gh.user_handler(self.login, force=True) class AuthUser(User): - """Github Current User object model.""" - - _ints = [ - 'id', 'public_repos', 'public_gists', 'followers', 'following', - 'total_private_repos', 'owned_private_repos', 'private_gists', - 'disk_usage', 'collaborators'] - _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) + """Github Authenticated User object model.""" - @property - def ri(self): - return ('user',) + #def handler(self): + # return self._gh.user_handler(self.login, force=True, private=True) def __repr__(self): - return '<model.AuthUser {0}>'.format(self.login) + return '<AuthUser %s>' % self.login |