Skip to content

Commit

Permalink
Project import
Browse files Browse the repository at this point in the history
  • Loading branch information
fridex committed Jan 4, 2018
0 parents commit 78cde94
Show file tree
Hide file tree
Showing 27 changed files with 1,318 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.pyc
githubcap.egg-info
build
dist
Empty file added Makefile
Empty file.
19 changes: 19 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[[source]]

url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"


[packages]

attrs = "*"
requests = "*"
click = "*"
daiquiri = "*"
voluptuous = "*"
pyyaml = "*"


[dev-packages]

113 changes: 113 additions & 0 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file added README.rst
Empty file.
1 change: 1 addition & 0 deletions githubcap-cli
12 changes: 12 additions & 0 deletions githubcap/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from .configuration import Configuration
from .enums import Filtering
from .enums import State
from .enums import Sorting
from .enums import SortingDirection
from .utils import setup_logging

__version__ = '1.0.0rc1'
__title__ = 'githubcap'
__author__ = 'Fridolin Pokorny'
__license__ = 'ASL 2.0'
__copyright__ = 'Copyright 2018 Fridolin Pokorny'
181 changes: 181 additions & 0 deletions githubcap/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import attr
from datetime import datetime
import copy
import enum
import logging
import requests
import time
import typing

from voluptuous import Schema

from .utils import parse_datetime
from .utils import serialize_datetime
from .configuration import Configuration
from .exceptions import MissingPassword
from .exceptions import HTTPError
from .utils import next_pagination_page
from .enums import GitHubCapEnum

_LOG = logging.getLogger(__name__)


@attr.s
class GitHubHandlerBase(object):
DEFAULT_PER_PAGE: typing.ClassVar[int] = Configuration().per_page_listing
config: typing.ClassVar[Configuration] = Configuration()

@classmethod
def call(cls, uri, payload=None, method=None):
requests_kwargs = {
'headers': copy.copy(Configuration().headers)
}

if Configuration().token:
_LOG.debug("Using OAuth2 token '%s***' for GitHub call", Configuration().token[:4])
requests_kwargs['headers']['Authorization'] = 'token {!s}'.format(Configuration().token)
elif Configuration().user:
if not Configuration().password:
raise MissingPassword("No password set for user {!s}".format(Configuration().user))

_LOG.debug("Using basic authentication for user %s", Configuration().user)
requests_kwargs['auth'].append((Configuration().user, Configuration().password))
else:
_LOG.debug("No authentication is used")

url = "{!s}/{!s}".format(Configuration().github_api, uri)
requests_func = getattr(requests, method.lower())
if payload:
requests_kwargs['json'] = payload

while True:
_LOG.debug("%s %s", method, url)
response = requests_func(url, **requests_kwargs)

_LOG.debug("Request took %s and the HTTP status code for response was %d",
response.elapsed, response.status_code)

if not (response.status_code == 403 and
response.json()['message'].startswith("API rate limit exceeded") and
cls.config.omit_rate_limiting):
break

reset_datetime = datetime.fromtimestamp(int(response.headers['X-RateLimit-Reset']))
sleep_time = (reset_datetime - datetime.now()).total_seconds()
_LOG.debug("API rate limit hit, retrying in %d seconds...", sleep_time)
time.sleep(sleep_time)

try:
# Rely on request's checks here
response.raise_for_status()
except requests.exceptions.HTTPError as exc:
raise HTTPError(response.json(), response.status_code) from exc

return response.json(), response.headers

@classmethod
def head(cls, *args, **kwargs):
return cls.call(*args, **kwargs, method='HEAD')

@classmethod
def get(cls, *args, **kwargs):
return cls.call(*args, **kwargs, method='GET')

@classmethod
def post(cls, *args, **kwargs):
return cls.call(*args, **kwargs, method='POST')

@classmethod
def patch(cls, *args, **kwargs):
return cls.call(*args, **kwargs, method='PATCH')

@classmethod
def put(cls, *args, **kwargs):
return cls.call(*args, **kwargs, method='PUT')

@classmethod
def delete(cls, *args, **kwargs):
return cls.call(*args, **kwargs, method='DELETE')

def _do_listing(self, base_uri):
while True:
uri = '{!s}?{!s}'.format(base_uri, self._get_query_string())
response, headers = self.get(uri)

for entry in response:
yield entry

if not self.config.pagination:
return

next_page = next_pagination_page(headers)
if next_page is None:
return
self.page = next_page

@classmethod
def submit(cls, item):
raise NotImplementedError


class GitHubBase(object):
_SCHEMA: typing.ClassVar[Schema] = None

# TODO: dirty flag

@classmethod
def from_response(cls, response):
if cls._SCHEMA is None:
raise NotImplementedError("No schema defined for entity {!r}".format(cls.__name__))

if Configuration().validate_schemas:
cls._SCHEMA(response)

return cls.from_dict(response)

@classmethod
def _from_dict_value(cls, attribute_type, value):
if attribute_type == datetime:
return parse_datetime(value)
elif issubclass(attribute_type, GitHubCapEnum):
return attribute_type.from_value(value)
elif issubclass(attribute_type, GitHubBase):
return attribute_type.from_dict(value)
elif '_gorg' in attribute_type.__dict__ and attribute_type._gorg == typing.List:
assert len(attribute_type.__args__) == 1, "Type defined multiple types: {!r}".format(attribute_type)
return list(cls._from_dict_value(attribute_type.__args__[0], item) for item in value)

return value

@classmethod
def from_dict(cls, dict_):
if dict_ is None:
return None

values = {}
for attribute in cls.__attrs_attrs__:
if attribute.name in dict_:
values[attribute.name] = cls._from_dict_value(attribute.type, dict_[attribute.name])

return cls(**values)

@classmethod
def _to_dict_value(cls, value):
if isinstance(value, datetime):
return serialize_datetime(value)
elif isinstance(value, GitHubBase):
return value.to_dict()
elif issubclass(value.__class__, enum.Enum):
return value.value
elif isinstance(value, list):
return list(cls._to_dict_value(item) for item in value)

return value

def to_dict(self):
result = {}
for attribute in self.__attrs_attrs__:
result[attribute.name] = self._to_dict_value(getattr(self, attribute.name))
return result


0 comments on commit 78cde94

Please sign in to comment.