diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7f79a37 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Aaron Madison +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md deleted file mode 100644 index 7591d60..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -apyclient -========= - -Python Api Client \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..4266112 --- /dev/null +++ b/README.rst @@ -0,0 +1,71 @@ +apyclient +========= + +A Python Api Client + +Overview +-------- + +Allows you to easily create client APIs in a highly customizable way. + + +Installation +------------ +Only requirement is python 2.6 or greater. Tests require 'mock' package. + +:: + + pip install apyclient + + +Usage +----- + +You are able to easily create a client Api class. The only requirement of +the Api class is that it must have a "HOST_NAME" attribute declared. The API +uses this host name to prepend to the endpoint when building the request. + +:: + + class MyApiClient(object): + HOST_NAME = "http://www.example.com/api + + @api_request("/api-endpoint/") + def fetch_some_stuff(some_var): + return {"the_variable": some_var} + + my_client = MyApiClient() + my_client.fetch_some_stuff(3) + +And that's it. The client will make an Http GET request by default with the +data provided by the decorated method. + +You can also do a POST request by declaring ``method="POST"`` in the api_request. + +:: + + @api_request("/api-endpoint/", method="POST") + def fetch_some_stuff(some_var): + return {"the_variable": some_var} + + +And finally, you are able to return a custom response class if you so desire. +Just either provide a ``RESPONSE_CLASS`` on the api client class, or a +``response_class`` on the api_request decorator. If you have a custom response +class declared both on the API client and on the api_request decorator, the +decorator will win because it is more specific. The response class must take +one argument on initialization, the original response. + +:: + + class MyApiClient(object): + HOST_NAME = "http://www.example.com/api + RESPONSE_CLASS = MyDefaultResponseClass + + @api_request("/api-endpoint/") + def fetch_some_stuff(some_var): + return {"the_variable": some_var} + + @api_request("/api-endpoint/", response_class=SpecializedResponseClass) + def fetch_some_stuff(some_var): + return {"the_variable": some_var} diff --git a/api.py b/apyclient.py similarity index 92% rename from api.py rename to apyclient.py index 442d3f1..c07d7d5 100644 --- a/api.py +++ b/apyclient.py @@ -1,3 +1,18 @@ +# apyclient.py +# A python client api library. +# Copyright (C) 2012 Aaron Madison + +# Released subject to the BSD License + + +__all__ = ( + 'api_request', + 'BaseResponse', + 'JSONApiResponse', +) + +__version__ = "0.0.1" + from functools import wraps import json diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0201c65 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ + +import os + +readme = os.path.join(os.path.dirname(__file__), 'README.rst') +LONG_DESCRIPTION = open(readme, 'r').read() + +params = dict( + name='apyclient', + version='0.0.1', + packages=[''], + url='https://github.com/madisona/apyclient', + license='BSD', + author='Aaron Madison', + author_email='aaron.l.madison@gmail.com', + description='A Python Api Client', + long_description=LONG_DESCRIPTION, + py_modules=['apyclient'], + + zip_safe=False, + classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Software Development :: Libraries :: Python Modules", + ], +) + + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup +else: + params['tests_require'] = ['unittest2', 'mock'] + params['test_suite'] = 'unittest2.collector' + +setup(**params) diff --git a/test_api.py b/test_apyclient.py similarity index 87% rename from test_api.py rename to test_apyclient.py index 4374657..96e00c7 100755 --- a/test_api.py +++ b/test_apyclient.py @@ -6,7 +6,7 @@ import urllib2 -import api +import apyclient __all__ = ( 'BaseResponseTests', @@ -35,15 +35,15 @@ def read(self): class ApiStub(object): HOST_NAME = "http://www.example.com" - @api.api_request("/do-something/", timeout=10) + @apyclient.api_request("/do-something/", timeout=10) def do_something(self): return {'times': 5} - @api.api_request("/do-multiple/", timeout=3) + @apyclient.api_request("/do-multiple/", timeout=3) def do_multiple(self): return {'times': [5, 3]} - @api.api_request("/do-post/", method="POST", timeout=30) + @apyclient.api_request("/do-post/", method="POST", timeout=30) def do_post(self): return { 'one_thing': "this&that", @@ -54,7 +54,7 @@ def do_post(self): class ApiCustomResponseStub(ApiStub): RESPONSE_CLASS = CustomResponseTwo - @api.api_request("/do-custom/", response_class=CustomResponse) + @apyclient.api_request("/do-custom/", response_class=CustomResponse) def do_custom(self): return {} @@ -63,18 +63,18 @@ class BaseResponseTests(TestCase): def test_status_code(self): r = ResponseStub(code=200) - response = api.BaseResponse(r) + response = apyclient.BaseResponse(r) self.assertEqual(200, response.code) def test_is_success(self): r = ResponseStub(code=200) - response = api.BaseResponse(r) + response = apyclient.BaseResponse(r) self.assertEqual(True, response.is_success) def test_not_success(self): - redirect = api.BaseResponse(ResponseStub(code=301)) - bad_request = api.BaseResponse(ResponseStub(code=400)) - server_error = api.BaseResponse(ResponseStub(code=500)) + redirect = apyclient.BaseResponse(ResponseStub(code=301)) + bad_request = apyclient.BaseResponse(ResponseStub(code=400)) + server_error = apyclient.BaseResponse(ResponseStub(code=500)) self.assertEqual(False, redirect.is_success) self.assertEqual(False, bad_request.is_success) @@ -82,13 +82,13 @@ def test_not_success(self): def test_returns_content(self): raw = ResponseStub(code=200, content="This is my content") - response = api.BaseResponse(raw) + response = apyclient.BaseResponse(raw) self.assertEqual(raw.content, response.content) def test_caches_response_content(self): raw = ResponseStub(code=200, content="This is my content") - response = api.BaseResponse(raw) + response = apyclient.BaseResponse(raw) with mock.patch.object(raw, 'read') as read: c = response.content @@ -169,14 +169,14 @@ def get_data(self): def test_loads_json_from_content(self): data = self.get_data() raw = ResponseStub(code=200, content=json.dumps(data)) - response = api.JSONApiResponse(raw) + response = apyclient.JSONApiResponse(raw) self.assertEqual(data, response.json()) def test_caches_json_response(self): data = self.get_data() raw = ResponseStub(code=200, content=json.dumps(data)) - response = api.JSONApiResponse(raw) + response = apyclient.JSONApiResponse(raw) with mock.patch('json.loads') as load: load.return_value = data @@ -186,10 +186,6 @@ def test_caches_json_response(self): self.assertEqual(1, load.call_count) - - - - if __name__ == '__main__': main()