diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0cdec8d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# http://krlmlr.github.io/using-gitattributes-to-avoid-merge-conflicts/ +/CHANGELOG.md merge=union diff --git a/.gitignore b/.gitignore index ed7288b..193bcd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ tags *.py[co] -.* MANIFEST dist/* agithub.egg-info/ build/ dist/ +.tox +.eggs diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fd869a5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: python +python: + - '3.7' + - '2.7' + +install: pip install -U tox-travis +script: tox diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fbcb84..736da7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.2.1] - 2019-10-07 +### Added +* Mozilla code of conduct +* Long description to setup.py containing README +* End to end GitHub unit test and tox testing with pytest +* Integration with Travis CI + +### Changed +* Moved to SCM (git) driven version instead of a hard coded one +* VERSION constant from semver list (e.g. [2, 2, 1]) to string version (e.g. 2.2.1) + +### Removed +* mock module to avoid collision with builtin mock module +* STR_VERSION constant + +### Fixed +* TypeError when paginate is `True` and `sleep_on_ratelimit` is the default (#66 by [@huma23](https://github.com/huma23)) + ## [2.2.0] - 2019-01-16 ### Added * GitHub pagination support, which can be enabled @@ -71,7 +89,8 @@ contributed!) * Has a version number. (Yippie!) * First more-or-less stable version -[Unreleased]: https://github.com/mozilla/agithub/compare/v2.2.0...HEAD +[Unreleased]: https://github.com/mozilla/agithub/compare/v2.2.1...HEAD +[2.2.1]: https://github.com/mozilla/agithub/compare/v2.2.0...v2.2.1 [2.2.0]: https://github.com/mozilla/agithub/compare/v2.1...v2.2.0 [2.1]: https://github.com/mozilla/agithub/compare/v2.0...v2.1 [2.0]: https://github.com/mozilla/agithub/compare/v1.3...v2.0 diff --git a/README.md b/README.md index 57b1875..cecbaac 100644 --- a/README.md +++ b/README.md @@ -427,5 +427,4 @@ become an expert on the code. From there, anything's possible. ## License Copyright 2012–2016 Jonathan Paugh and contributors -See [COPYING][LIC] for license details -[LIC]: COPYING +See [COPYING](COPYING) for license details diff --git a/agithub/GitHub.py b/agithub/GitHub.py index 8f96bd9..485ab9b 100644 --- a/agithub/GitHub.py +++ b/agithub/GitHub.py @@ -5,10 +5,12 @@ import re import logging -from agithub.base import API, ConnectionProperties, Client, RequestBody, ResponseBody +from agithub.base import ( + API, ConnectionProperties, Client, RequestBody, ResponseBody) logger = logging.getLogger(__name__) + class GitHub(API): """ The agnostic GitHub API. It doesn't know, and you don't care. @@ -92,7 +94,7 @@ def request(self, method, url, bodyData, headers): if 'content-type' in headers: del headers['content-type'] - #TODO: Context manager + # TODO: Context manager requestBody = RequestBody(bodyData, headers) if self.sleep_on_ratelimit and self.no_ratelimit_remaining(): @@ -107,12 +109,14 @@ def request(self, method, url, bodyData, headers): self.headers = response.getheaders() conn.close() - if status == 403 and self.sleep_on_ratelimit and self.no_ratelimit_remaining(): + if (status == 403 and self.sleep_on_ratelimit and + self.no_ratelimit_remaining()): self.sleep_until_more_ratelimit() else: data = content.processBody() if self.paginate and type(data) == list: - data.extend(self.get_additional_pages(method, bodyData, headers)) + data.extend( + self.get_additional_pages(method, bodyData, headers)) return status, data def get_additional_pages(self, method, bodyData, headers): @@ -165,9 +169,10 @@ def get_next_link_url(self): """Given a set of HTTP headers find the RFC 5988 Link header field, determine if it contains a relation type indicating a next resource and if so return the URL of the next resource, otherwise return an empty - string.""" + string. - # From https://github.com/requests/requests/blob/master/requests/utils.py + From https://github.com/requests/requests/blob/master/requests/utils.py + """ for value in [x[1] for x in self.headers if x[0].lower() == 'link']: replace_chars = ' \'"' value = value.strip(replace_chars) diff --git a/agithub/__init__.py b/agithub/__init__.py index e47e18b..c43ae91 100644 --- a/agithub/__init__.py +++ b/agithub/__init__.py @@ -1,5 +1,5 @@ # Copyright 2012-2016 Jonathan Paugh and contributors # See COPYING for license details -from agithub.base import VERSION, STR_VERSION +from agithub.base import VERSION -__all__ = ["VERSION", "STR_VERSION"] +__all__ = ["VERSION"] diff --git a/agithub/agithub_test.py b/agithub/agithub_test.py index 8be4d82..ad79144 100755 --- a/agithub/agithub_test.py +++ b/agithub/agithub_test.py @@ -3,10 +3,33 @@ # See COPYING for license details from agithub.GitHub import GitHub from agithub.base import IncompleteRequest -import mock import unittest +class Client(object): + http_methods = ('demo', 'test') + + def __init__(self, username=None, password=None, token=None, + connection_properties=None): + pass + + def setConnectionProperties(self, props): + pass + + def demo(self, *args, **params): + return self.methodCalled('demo', *args, **params) + + def test(self, *args, **params): + return self.methodCalled('test', *args, **params) + + def methodCalled(self, methodName, *args, **params): + return { + 'methodName': methodName, + 'args': args, + 'params': params + } + + class TestGitHubObjectCreation(unittest.TestCase): def test_user_pw(self): gh = GitHub('korfuri', '1234') @@ -30,7 +53,7 @@ def test_token_password(self): class TestIncompleteRequest(unittest.TestCase): def newIncompleteRequest(self): - return IncompleteRequest(mock.Client()) + return IncompleteRequest(Client()) def test_pathByGetAttr(self): rb = self.newIncompleteRequest() @@ -64,5 +87,18 @@ def test_callMethodTest(self): } ) + +def test_github(): + g = GitHub() + status, data = g.users.octocat.get() + assert data.get('name') == 'The Octocat' + assert status == 200 + # Workaround to https://github.com/mozilla/agithub/issues/67 + response_headers = dict([(x.lower(), y) for x, y in g.getheaders()]) + assert ( + response_headers.get('Content-Type'.lower()) == + 'application/json; charset=utf-8') + + if __name__ == '__main__': unittest.main() diff --git a/agithub/base.py b/agithub/base.py index 555a1c6..454382a 100644 --- a/agithub/base.py +++ b/agithub/base.py @@ -2,6 +2,7 @@ # See COPYING for license details import json from functools import partial, update_wrapper +from setuptools_scm import get_version import sys if sys.version_info[0:2] > (3, 0): @@ -14,14 +15,13 @@ class ConnectionError(OSError): pass -VERSION = [2, 1] -STR_VERSION = 'v' + '.'.join(str(v) for v in VERSION) +VERSION = get_version(root='..', relative_to=__file__) # These headers are implicitly included in each request; however, each # can be explicitly overridden by the client code. (Used in Client # objects.) _default_headers = { - 'user-agent': 'agithub/' + STR_VERSION, + 'user-agent': 'agithub/' + VERSION, 'content-type': 'application/json' } diff --git a/agithub/mock.py b/agithub/mock.py deleted file mode 100644 index 3cfa4c6..0000000 --- a/agithub/mock.py +++ /dev/null @@ -1,27 +0,0 @@ -# Mock objects for testing -# Copyright 2012-2016 Jonathan Paugh and contributors -# See COPYING for license details - - -class Client(object): - http_methods = ('demo', 'test') - - def __init__(self, username=None, password=None, token=None, - connection_properties=None): - pass - - def setConnectionProperties(self, props): - pass - - def demo(self, *args, **params): - return self.methodCalled('demo', *args, **params) - - def test(self, *args, **params): - return self.methodCalled('test', *args, **params) - - def methodCalled(self, methodName, *args, **params): - return { - 'methodName': methodName, - 'args': args, - 'params': params - } diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 23fcba9..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - diff --git a/setup.py b/setup.py index f1570b6..4ba0edf 100755 --- a/setup.py +++ b/setup.py @@ -1,10 +1,20 @@ from setuptools import setup, find_packages +from os import path -version = '2.2.0' +here = path.abspath(path.dirname(__file__)) +with open(path.join(here, 'README.md')) as f: + long_description = f.read() + +test_requirements = ['pytest'] + +extras = { + "test": test_requirements, +} setup(name='agithub', - version=version, description="A lightweight, transparent syntax for REST clients", + long_description=long_description, + long_description_content_type='text/markdown', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', @@ -17,9 +27,14 @@ keywords=['api', 'REST', 'GitHub', 'Facebook', 'SalesForce'], author='Jonathan Paugh', author_email='jpaugh@gmx.us', - url='https://github.com/jpaugh/agithub', + url='https://github.com/mozilla/agithub', license='MIT', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=False, + tests_require=test_requirements, + extras_require=extras, + setup_requires=['setuptools-scm'], + use_scm_version=True, + install_requires=['setuptools-scm'], ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..4f4da62 --- /dev/null +++ b/tox.ini @@ -0,0 +1,19 @@ +[tox] +envlist = py27, py37, flake8 + +[travis] +python = + 3.7: py37 + 2.7: py27 + +[testenv:flake8] +basepython = python +deps = flake8 +commands = flake8 agithub setup.py + +[testenv] +setenv = + PYTHONPATH = {toxinidir} +deps = .[test] +commands = + pytest {posargs}