aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Medina <davidmedina9@gmail.com>2012-02-03 03:02:37 +0100
committerDavid Medina <davidmedina9@gmail.com>2012-02-03 03:02:37 +0100
commit2e2e8bf5b75c28321ebe5c08fa077d431c408952 (patch)
treeed2e82884f1e0281a9ac8a3820a17e4c71e16681
parentReset project (diff)
downloadpython-github3-2e2e8bf5b75c28321ebe5c08fa077d431c408952.tar.xz
python-github3-2e2e8bf5b75c28321ebe5c08fa077d431c408952.zip
Complete new redesign trying to code less coupled
and I think that I'm getting it :P Log from another repo (rebase hell if I'd try) ------------------------------------------------- d64647909aa3ddde6532994f3bd895e297dd1c1d +Model mapper prototype 53fa5c47c14ebf405ac11af0b21b3bc4f1251558 Complete client 0af9075da9fddd14f0c04cb3ad5a74f33a6dcaf7 Fix exceptions typo eae8232b4235451bac344f0114895dcf159d3af5 +ResouceFactory (renamed from uris) 14c66bea1bf0a68fd540d1b73a8c25aa69c854ce WIP on Uri Factory 8012908b795d598510a7e4d288f40b324e751aab Fix client instance 3e97153664365146ac558ada22bb9d3fc1a3189d Fast night services prototype 785a0bdc2ddd815d911255ba7e91c5d71cc82a74 fix typo 84f03a039fb2413f2e156d3ec0bd226e65bcae31 WIP on client f53076153d96b492f23e01f91debe8fafbc4cfe0 Trying wrapping requests library for our needs 52ea9f888048853760990053211c9e0c4440b810 Initial commit
-rw-r--r--AUTHORS7
-rw-r--r--README.md4
-rw-r--r--github3/core/__init__.py0
-rw-r--r--github3/core/client.py96
-rw-r--r--github3/core/resources/__init__.py81
-rw-r--r--github3/core/resources/user/__init__.py2
-rw-r--r--github3/core/resources/user/emails.py8
-rw-r--r--github3/core/resources/user/followers.py0
-rw-r--r--github3/core/resources/user/keys.py0
-rw-r--r--github3/core/resources/user/user.py23
-rw-r--r--github3/errors.py41
-rw-r--r--github3/models/__init__.py0
-rw-r--r--github3/models/base.py57
-rw-r--r--github3/models/user.py20
-rw-r--r--github3/services/__init__.py0
-rw-r--r--github3/services/base.py41
-rw-r--r--github3/services/user.py48
-rw-r--r--github3/tests/__init__.py0
-rw-r--r--github3/tests/test_errors.py36
-rw-r--r--reqs.txt5
20 files changed, 463 insertions, 6 deletions
diff --git a/AUTHORS b/AUTHORS
index 29bea31..d16bd21 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,12 +1,15 @@
-python-github3 is written and maintained by Kenneth Reitz and
+python-github3 fork is written and maintained by David Medina and
various contributors:
Development Lead
````````````````
-- Kenneth Reitz <me@kennethreitz.com>
- David Medina <davidmedina9@gmail.com>
+- Francisco Marcos <fmarcos83@gmail.com>
+-----------------------------------
+Inspired by Kenneth Reitz's fork
+-----------------------------------
Patches and Suggestions
```````````````````````
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..47d2653
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+# Python github3 wrapper
+
+Under heavy development
+
diff --git a/github3/core/__init__.py b/github3/core/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/github3/core/__init__.py
diff --git a/github3/core/client.py b/github3/core/client.py
new file mode 100644
index 0000000..99a88f0
--- /dev/null
+++ b/github3/core/client.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+import requests
+from errors import GithubError
+
+
+VALID_REQUEST_ARGS = set((
+ 'params', 'data', 'headers', 'cookies', 'files', 'auth', 'timeout',
+ 'allow_redirects', 'proxies', 'return_response', 'config',
+ 'prefetch', 'verify'))
+
+
+class Client(object):
+ """ Client to send configurated requests"""
+
+ def __init__(self, **kwargs):
+ """
+ It can be configurated
+
+ :login, :password, :user, :repo, :token, :per_page, :base_url
+ """
+
+ self.requester = requests.session()
+ self.config = {
+ 'per_page': 100,
+ 'base_url': 'https://api.github.com/'
+ }
+ self.config.update(kwargs)
+ self.set_credentials(self.config.get('login'),
+ self.config.get('password'))
+ self.set_token(self.config.get('token'))
+ self.__set_params(self.config)
+
+ @property
+ def user(self):
+ return self.config.get('user')
+
+ @user.setter
+ def set_user(self, user):
+ self.config['user'] = user
+
+ @property
+ def repo(self):
+ return self.config.get('repo')
+
+ @repo.setter
+ def set_repo(self, repo):
+ self.config['repo'] = repo
+
+ def set_credentials(self, login, password):
+ if login and password:
+ self.requester.auth = (login, password)
+
+ def set_token(self, token):
+ if token:
+ self.requester.params['access_token'] = token
+
+ def __set_params(self, config):
+ self.requester.params['per_page'] = config.get('per_page')
+
+ def __parse_kwargs(func):
+ """ Decorator to put extra args into requests.params """
+
+ def wrapper(self, verb, resource, **kwargs):
+ diffs = kwargs.viewkeys() - VALID_REQUEST_ARGS
+ new_params = kwargs.get('params') or {}
+ new_params.update({key:kwargs[key] for key in diffs})
+ kwargs['params'] = new_params
+ return func(self, verb, resource, **kwargs)
+ return wrapper
+
+ @__parse_kwargs
+ def request(self, verb, resource, **kwargs):
+ resource = "%s%s" % (self.config['base_url'], resource)
+ response = self.requester.request(verb, resource, **kwargs)
+ GithubError(response).process()
+ return response
+
+ def get(self, resource, **kwargs):
+ return self.request('get', resource, **kwargs)
+
+ def post(self, resource, **kwargs):
+ return self.request('post', resource, **kwargs)
+
+ def patch(self, resource, **kwargs):
+ return self.request('patch', resource, **kwargs)
+
+ def put(self, resource, **kwargs):
+ return self.request('put', resource, **kwargs)
+
+ def delete(self, resource, **kwargs):
+ return self.request('delete', resource, **kwargs)
+
+ def head(self, resource, **kwargs):
+ return self.request('head', resource, **kwargs)
diff --git a/github3/core/resources/__init__.py b/github3/core/resources/__init__.py
new file mode 100644
index 0000000..c5dee18
--- /dev/null
+++ b/github3/core/resources/__init__.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+import re
+
+class UriNotFound(Exception):
+ pass
+
+
+class UriInvalid(Exception):
+ pass
+
+
+class Resource(object):
+ """ """
+
+ def __init__(self, args):
+ """ """
+ self.args = args
+ self.validate()
+ self.uri = self.set_uri()
+
+ def validate(self, args):
+ raise NotImplementedError
+
+ def set_uri(self):
+ raise NotImplementedError
+
+ def get_uri(self):
+ return str(self.uri).strip('/')
+
+ def get_model(self):
+ return getattr(self, 'model', '')
+
+ def __getattr__(self, name):
+ return self.args.get(name)
+
+ def __str__(self):
+ return self.get_uri()
+
+
+class Factory(object):
+ """ """
+ import_pattern = re.compile(r'^(\w+\.)+\w+$')
+
+ def __init__(self, **kwargs):
+ self.args = kwargs
+
+ def __validate(func):
+ """ """
+
+ def wrapper(self, resource_path):
+ if not Factory.import_pattern.match(resource_path):
+ raise UriInvalid("'%s' isn't valid form" % resource_path)
+ return func(self, resource_path.lower())
+ return wrapper
+
+ def __dispatch(func):
+ """ """
+
+ from importlib import import_module
+ def wrapper(self, resource_path):
+ module_chunk, s, uri_chunk = resource_path.rpartition('.')
+ try:
+ # TODO: CamelCase and under_score support, now only Class Name
+ module = import_module('core.resources.%s' % module_chunk)
+ uri = getattr(module, uri_chunk.capitalize())
+ except ImportError:
+ raise UriNotFound("'%s' module does not exists" % module_chunk)
+ except AttributeError:
+ raise UriNotFound("'%s' uri doesn't exists into '%s' module"
+ % (uri_chunk.capitalize(), module_chunk))
+ return func(self, uri)
+ return wrapper
+
+ @__validate
+ @__dispatch
+ def __call__(self, resource_class=''):
+ resource = resource_class(self.args)
+ assert isinstance(resource, Resource)
+ return resource
diff --git a/github3/core/resources/user/__init__.py b/github3/core/resources/user/__init__.py
new file mode 100644
index 0000000..8248571
--- /dev/null
+++ b/github3/core/resources/user/__init__.py
@@ -0,0 +1,2 @@
+from core.resources import Resource
+from user import *
diff --git a/github3/core/resources/user/emails.py b/github3/core/resources/user/emails.py
new file mode 100644
index 0000000..83644db
--- /dev/null
+++ b/github3/core/resources/user/emails.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from core.uris import Manager
+from models.base import Model
+
+class List(Manager):
+ pass
diff --git a/github3/core/resources/user/followers.py b/github3/core/resources/user/followers.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/github3/core/resources/user/followers.py
diff --git a/github3/core/resources/user/keys.py b/github3/core/resources/user/keys.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/github3/core/resources/user/keys.py
diff --git a/github3/core/resources/user/user.py b/github3/core/resources/user/user.py
new file mode 100644
index 0000000..4308f01
--- /dev/null
+++ b/github3/core/resources/user/user.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from . import Resource
+from models.user import User
+
+__all__ = ('Get', 'Update')
+
+class Get(Resource):
+
+ model = User
+
+ def validate(self):
+ pass
+
+ def set_uri(self):
+ if self.user:
+ return 'users/%s' % self.user
+ else:
+ return 'user'
+
+class Update(Resource):
+ pass
diff --git a/github3/errors.py b/github3/errors.py
new file mode 100644
index 0000000..0d58c16
--- /dev/null
+++ b/github3/errors.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+import json
+
+
+class BadRequest(Exception):
+ pass
+
+
+class UnprocessableEntity(Exception):
+ pass
+
+
+class GithubError(object):
+ """ Handler for API errors """
+
+ def __init__(self, response):
+ self.response = response
+ self.status_code = response.status_code
+ try:
+ self.debug = json.loads(response.content)
+ except (ValueError, TypeError):
+ self.debug = {'message': response.content}
+
+ def error_400(self):
+ return BadRequest("400 - %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 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()
+ self.response.raise_for_status()
diff --git a/github3/models/__init__.py b/github3/models/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/github3/models/__init__.py
diff --git a/github3/models/base.py b/github3/models/base.py
new file mode 100644
index 0000000..a2c3ac7
--- /dev/null
+++ b/github3/models/base.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+class Model(object):
+
+ dates = ()
+ maps = {}
+ collection_maps = {}
+
+ def __init__(self, attrs):
+ """ """
+ self.attrs = attrs
+
+ def __getattr__(self, name):
+ try:
+ return self.attrs[name]
+ except KeyError:
+ raise AttributeError
+
+ @classmethod
+ def loads(self, raw_resource):
+ def parse_date(string_date):
+ from datetime import datetime
+ try:
+ date = datetime.strptime(string_date, '%Y-%m-%dT%H:%M:%SZ')
+ except TypeError:
+ date = None
+ return date
+
+ def parse_map(model, raw_resource):
+ if hasattr(raw_resource, 'items'):
+ return model.loads(raw_resource)
+
+ def parse_collection_map(model, raw_resources):
+ # Dict of resources (Ex: Gist file)
+ if hasattr(raw_resources, 'items'):
+ dict_map = {}
+ for key, raw_resource in raw_resources.items():
+ dict_map[key] = model.loads(raw_resource)
+ return dict_map
+ # list of resources
+ elif hasattr(raw_resources, '__iter__'):
+ return [model.loads(raw_resource)
+ for raw_resource in raw_resources]
+ raw_resource.update(
+ {attr: parse_date(raw_resource[attr])
+ for attr in self.dates if attr in raw_resource})
+ raw_resource.update(
+ {attr: parse_map(model, raw_resource[attr])
+ for attr, model in self.maps.items()
+ if attr in raw_resource})
+ raw_resource.update(
+ {attr: parse_collection_map(model, raw_resource[attr])
+ for attr, model in self.collection_maps.items()
+ if attr in raw_resource})
+
+ return self(raw_resource)
diff --git a/github3/models/user.py b/github3/models/user.py
new file mode 100644
index 0000000..c87afc3
--- /dev/null
+++ b/github3/models/user.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from base import Model
+
+
+class Plan(Model):
+
+ def __str__(self):
+ return '<Plan (%s)>' % getattr(self, 'name', '')
+
+
+class User(Model):
+ """ """
+
+ maps = {'plan': Plan}
+ dates = ('created_at', )
+
+ def __str__(self):
+ return '<User (%s)>' % getattr(self, 'login', '')
diff --git a/github3/services/__init__.py b/github3/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/github3/services/__init__.py
diff --git a/github3/services/base.py b/github3/services/base.py
new file mode 100644
index 0000000..c74aa2e
--- /dev/null
+++ b/github3/services/base.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from core.client import Client
+
+class Base(object):
+
+ def __init__(self, **config):
+ self.client = Client(**config)
+
+ def get_user(self):
+ return self.client.user
+
+ def set_user(self, user):
+ self.client.user = user
+
+ def get_repo(self):
+ return self.client.repo
+
+ def set_repo(self, repo):
+ self.client.repo = repo
+
+ def _get_result(self, resource, **kwargs):
+ return Result(self.client.get, resource, **kwargs)
+
+class Result(object): # move
+
+ def __init__(self, method, resource, **kwargs):
+ self.method = method
+ self.resource= resource
+ self.args = kwargs
+
+ def __repr__(self):
+ pass
+
+ def process(self):
+ model = self.resource.get_model()
+ raw = self.method(self.resource, **self.args)
+ if model:
+ import json
+ return model.loads(json.loads(raw.content))
diff --git a/github3/services/user.py b/github3/services/user.py
new file mode 100644
index 0000000..0a7e740
--- /dev/null
+++ b/github3/services/user.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from base import Base
+from core.resources import Factory
+
+
+class Keys(Base):
+
+ def list(self):
+ return self.get_resource('user/keys')
+
+
+class Followers(Base):
+
+ def list(self, user):
+ user = user or self.client.user
+ if user:
+ return self.get_resource('users/%s/followers' % user)
+ else:
+ return self.get_resource('user/followers')
+
+
+class Emails(Base):
+
+ def list(self):
+ return self.get_resource('user/emails')
+
+ def add(self):
+ pass
+
+ def delete(self):
+ pass
+
+class User(Base):
+
+ def __init__(self, **kwargs):
+ self.keys = Keys(**kwargs)
+ self.emails = Emails(**kwargs)
+ self.followers = Followers(**kwargs)
+ super(User, self).__init__(**kwargs)
+
+ def get(self, user):
+ resource = Factory(user=user or self.client.user)
+ return self._get_result(resource('user.Get'))
+
+ def update(self):
+ pass
diff --git a/github3/tests/__init__.py b/github3/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/github3/tests/__init__.py
diff --git a/github3/tests/test_errors.py b/github3/tests/test_errors.py
new file mode 100644
index 0000000..36f33d9
--- /dev/null
+++ b/github3/tests/test_errors.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+from unittest import TestCase
+from requests.exceptions import HTTPError
+from core import client
+import errors
+import json
+
+class TestErrorsWithoutAuth(TestCase):
+ """docstring for TestRequestsLibrary"""
+
+ def setUp(self):
+ self.client = client.Client()
+
+ def test_malformed_url(self):
+ self.assertRaises(HTTPError, self.client.request, 'get', 'fake')
+
+class TestErrorsAuthenticated(TestCase):
+ """docstring for TestErrorsAuthenticaed"""
+
+ def setUp(self):
+ self.client = client.Client(
+ login='pygit',
+ password='pygithub3'
+ )
+
+ def test_400_parsing_json(self):
+ data = 'strinf'
+ self.assertRaises(errors.BadRequest, self.client.request,
+ 'post', 'user/repos', data=data)
+
+ def test_400_json_hash(self):
+ data = json.dumps({'names': 'david'})
+ with self.assertRaises(errors.UnprocessableEntity) as cm:
+ self.client.request('post', 'user/repos', data=data)
diff --git a/reqs.txt b/reqs.txt
index 516c4b0..f229360 100644
--- a/reqs.txt
+++ b/reqs.txt
@@ -1,4 +1 @@
-requests==0.7.4
-python-dateutil==2.0
-mock==0.7.2
-nose
+requests