From 249921b8533e8d5481ec1bcdf2372f291f759f8e Mon Sep 17 00:00:00 2001 From: Alex Khomchenko Date: Sun, 2 Aug 2015 14:07:25 +0300 Subject: [PATCH 1/4] py3 support is fixed, test infrastructure is added * fix py3 support * add tox * add editorconfig * add venv and ide ignore --- .editorconfig | 14 ++++++++++++++ .gitignore | 6 ++++++ .travis.yml | 15 +++++++++------ MANIFEST.in | 1 + dev-requirements.txt | 3 +++ moves/__init__.py | 2 +- requirements.txt | 1 - setup.py | 9 ++++++++- tests/test_moves.py | 4 ++++ tox.ini | 13 +++++++++++++ 10 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 .editorconfig create mode 100644 MANIFEST.in create mode 100644 dev-requirements.txt delete mode 100644 requirements.txt create mode 100644 tests/test_moves.py create mode 100644 tox.ini diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e68c4d5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 39840c6..c6e8039 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,9 @@ nosetests.xml .mr.developer.cfg .project .pydevproject + +# Virtual env +venv + +# IDE +.idea diff --git a/.travis.yml b/.travis.yml index 7741bd0..32fcb5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,14 @@ language: python python: - "2.6" - "2.7" - - "3.2" + - "pypy" - "3.3" -# command to install dependencies -install: "pip install -r requirements.txt" -# command to run tests + - "3.4" + +install: + - pip install tox + +# thx to jinja for this script: - - python setup.py install - - python setup.py test + - tox -e \ + $(echo py$TRAVIS_PYTHON_VERSION | tr -d . | sed -e 's/pypypy/pypy/') diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..bb3ec5f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.md diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..4aeacd1 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,3 @@ +tox +pytest +-e . \ No newline at end of file diff --git a/moves/__init__.py b/moves/__init__.py index 69967e0..45edbe5 100644 --- a/moves/__init__.py +++ b/moves/__init__.py @@ -1 +1 @@ -from _moves import * +from ._moves import * diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b7e0446..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests>=0.11 diff --git a/setup.py b/setup.py index ce1d1f3..7b858c2 100644 --- a/setup.py +++ b/setup.py @@ -10,15 +10,22 @@ url='https://github.com/lysol/moves', download_url='http://github.com/lysol/moves/archive/v0.1.tar.gz', packages=['moves'], - install_requires=open('requirements.txt').read(), + install_requires=[ + 'requests>=0.11' + ], long_description=open('README.md').read(), + include_package_data=True, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'Topic :: Software Development :: Libraries :: Python Modules', ], license='MIT' diff --git a/tests/test_moves.py b/tests/test_moves.py new file mode 100644 index 0000000..455bb29 --- /dev/null +++ b/tests/test_moves.py @@ -0,0 +1,4 @@ +# coding: utf-8 + + +import moves # TODO: write tests, this one checks compatibility diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..b0197f4 --- /dev/null +++ b/tox.ini @@ -0,0 +1,13 @@ +[tox] +envlist = py26, py27, pypy, py33, py34 + +[testenv] +deps = + pytest + responses + +commands = + py.test [] + +[pytest] +norecursedirs = venv .tox examples *.egg-info From 39138fd22981cf296b68fdf67d7314d756ec9861 Mon Sep 17 00:00:00 2001 From: Alex Khomchenko Date: Sun, 2 Aug 2015 14:58:54 +0300 Subject: [PATCH 2/4] add sample test --- tests/test_moves.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_moves.py b/tests/test_moves.py index 455bb29..342a2a1 100644 --- a/tests/test_moves.py +++ b/tests/test_moves.py @@ -1,4 +1,15 @@ # coding: utf-8 -import moves # TODO: write tests, this one checks compatibility +import pytest + +import moves + + +class TestMovesClient(object): + def test_raises_if_no_acess_token_is_provided(self): + client = moves.MovesClient() + with pytest.raises(moves.MovesAPIError) as err: + client.api('/user') + + assert 'provide a valid access token' in str(err.value) From 3d1604537bd88e91c3fb3d07072a7d2cd10fe654 Mon Sep 17 00:00:00 2001 From: Alex Khomchenko Date: Sun, 2 Aug 2015 15:28:28 +0300 Subject: [PATCH 3/4] non-spaming travis configuration --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 32fcb5b..7928816 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,3 +13,8 @@ install: script: - tox -e \ $(echo py$TRAVIS_PYTHON_VERSION | tr -d . | sed -e 's/pypypy/pypy/') + +notifications: + email: + on_success: change + on_failure: always From 5cba08cb409cbb8766d80c443e66a9d83081b755 Mon Sep 17 00:00:00 2001 From: Alex Khomchenko Date: Sun, 2 Aug 2015 17:57:09 +0300 Subject: [PATCH 4/4] fix dynamic function creation --- dev-requirements.txt | 3 ++- moves/_moves.py | 42 ++++++++++++++++++------------------------ tests/test_moves.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 4aeacd1..3247b7e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ tox pytest --e . \ No newline at end of file +responses +-e . diff --git a/moves/_moves.py b/moves/_moves.py index 369f79c..521cf89 100644 --- a/moves/_moves.py +++ b/moves/_moves.py @@ -22,9 +22,9 @@ class MovesClient(object): token_url = "https://api.moves-app.com/oauth/v1/access_token" tokeninfo_url = "https://api.moves-app.com/oauth/v1/tokeninfo" refresh_url = "https://api.moves-app.com/oauth/v1/access_token" - - - + + + def __init__(self, client_id=None, client_secret=None, access_token=None, use_app=False): @@ -93,7 +93,7 @@ def refresh_oauth_token(self, refresh_token, **kwargs): raise MovesAPIError(error) def tokeninfo(self): - + params = { 'access_token': self.access_token } @@ -129,7 +129,7 @@ def api(self, path, method='GET', **kwargs): if 'etag' in params: headers['If-None-Match'] = params['etag'] del(params['etag']) - + resp = requests.request(method, url, data=data, params=params, @@ -139,7 +139,7 @@ def api(self, path, method='GET', **kwargs): resp.status_code, resp.text) if resp.status_code == 304: raise MovesAPINotModifed("Unmodified") - + self._last_headers = resp.headers return resp @@ -157,34 +157,28 @@ def set_first_date(self): self.first_date = response['profile']['firstDate'] def __getattr__(self, name): - '''\ -Turns method calls such as "moves.foo_bar(...)" into -a call to "moves.api('/foo/bar', 'GET', params={...})" -and then parses the response. -''' + """ + Turns method calls such as "moves.foo_bar(...)" into + a call to "moves.api('/foo/bar', 'GET', params={...})" + and then parses the response. + """ base_path = name.replace('_', '/') - # Define a function that does what we want. + # Define a function that does what we want. def closure(*path, **params): - 'Accesses the /%s API endpoints.' + """Accesses the /%s API endpoints.""" path = list(path) path.insert(0, base_path) return self.parse_response( self.api('/'.join(path), 'GET', params=params) ) - # Clone a new method with the correct name and doc string. - retval = types.FunctionType( - closure.func_code, - closure.func_globals, - name, - closure.func_defaults, - closure.func_closure) - retval.func_doc = closure.func_doc % base_path + closure.__name__ = name + closure.__doc__ = closure.__doc__ % base_path # Cache it to avoid additional calls to __getattr__. - setattr(self, name, retval) - return retval + setattr(self, name, closure) + return closure # Give Access to last attribute _move_client_status = ['etag', 'x-ratelimit-hourlimit', 'x-ratelimit-hourremaining', @@ -193,4 +187,4 @@ def closure(*path, **params): att = att.replace('-', '_') setattr(MovesClient, att, property(lambda self,att=att: self._last_headers.get(att, None) if self._last_headers else att)) - + diff --git a/tests/test_moves.py b/tests/test_moves.py index 342a2a1..f6237f7 100644 --- a/tests/test_moves.py +++ b/tests/test_moves.py @@ -2,6 +2,7 @@ import pytest +import responses import moves @@ -13,3 +14,32 @@ def test_raises_if_no_acess_token_is_provided(self): client.api('/user') assert 'provide a valid access token' in str(err.value) + + @responses.activate + def test_performs_call_to_api_if_access_token_is_provided(self): + responses.add(responses.GET, + url='https://api.moves-app.com/api/1.1/user/profile', + status=200, body='{"success": true, "id": 1}', + content_type='application/json') + + client = moves.MovesClient(access_token='secret') + + resp = client.user_profile() + + assert resp == dict(success=True, id=1) + assert (responses.calls[0].request.headers.get('authorization') + == 'Bearer secret') + + @responses.activate + def test_performs_call_to_api_with_overriden_token(self): + responses.add(responses.GET, + url='https://api.moves-app.com/api/1.1/user/profile', + status=200, body='{"success": true}', + content_type='application/json') + + client = moves.MovesClient(access_token='secret') + + client.user_profile(access_token='new-secret') + + assert (responses.calls[0].request.headers.get('authorization') + == 'Bearer new-secret')