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..7928816 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,19 @@ 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/') + +notifications: + email: + on_success: change + on_failure: always 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..3247b7e --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,4 @@ +tox +pytest +responses +-e . 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/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/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..f6237f7 --- /dev/null +++ b/tests/test_moves.py @@ -0,0 +1,45 @@ +# coding: utf-8 + + +import pytest +import responses + +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) + + @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') 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