-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Versioning potion-client solves simplejson issue (#111)
- Loading branch information
Showing
23 changed files
with
947 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ ignore_errors = True | |
|
||
omit = | ||
onecodex/version.py | ||
onecodex/vendored/* | ||
|
||
[html] | ||
directory = htmlcov |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
""" | ||
Modules created by others. Versioned and distributed by One Codex | ||
""" | ||
|
||
__all__ = ['potion_client'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
Copyright (c) 2016 The Novo Nordisk Foundation Center for Biosustainability | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
this software and associated documentation files (the "Software"), to deal in | ||
the Software without restriction, including without limitation the rights to | ||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
the Software, and to permit persons to whom the Software is furnished to do so, | ||
subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
|
||
============= | ||
Potion client | ||
============= | ||
|
||
|
||
.. image:: https://img.shields.io/travis/biosustain/potion-client/new-potion-client.svg?style=flat-square | ||
:target: https://travis-ci.org/biosustain/potion-client | ||
|
||
.. image:: https://img.shields.io/coveralls/biosustain/potion-client/new-potion-client.svg?style=flat-square | ||
:target: https://coveralls.io/r/biosustain/potion-client | ||
|
||
.. image:: https://img.shields.io/pypi/v/Potion-Client.svg?style=flat-square | ||
:target: https://pypi.python.org/pypi/Potion-Client | ||
|
||
.. image:: https://img.shields.io/pypi/l/Potion-Client.svg?style=flat-square | ||
:target: https://pypi.python.org/pypi/Potion-Client | ||
|
||
.. image:: https://badges.gitter.im/Join%20Chat.svg | ||
:alt: Join the chat at https://gitter.im/biosustain/potion | ||
:target: https://gitter.im/biosustain/potion?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge | ||
|
||
Description | ||
=========== | ||
|
||
This is a Python client for APIs written in `Flask-Potion <https://github.com/biosustain/potion>`_ (a powerful Flask extension for self-documenting JSON APIs). | ||
|
||
The package uses `Requests <https://github.com/kennethreitz/requests>`_ to provide a super-simple interface to Potion APIs that | ||
works with all common authentication methods. It generates classes for each of the resources in the API and automatically handles pagination | ||
and resolving and serializing references. It also has some basic `IPython Notebook <http://ipython.org/notebook.html>`_ support. | ||
|
||
Example | ||
======= | ||
|
||
.. code-block:: python | ||
from potion_client import Client | ||
from potion_client.auth import HTTPBearerAuth | ||
from potion_client.exceptions import ItemNotFound | ||
client = Client('http://localhost/api', auth=HTTPBearerAuth('79054025255fb1a26e4bc422aef54eb4')) | ||
u123 = client.User(123) | ||
chomp = client.Animal() | ||
chomp.owner = u123 | ||
chomp.name = "Chomp" | ||
chomp.species = "hamster" | ||
chomp.save() | ||
pets = client.Animal.instances(where={"owner": u123}, sort={"created_at": True}) | ||
print("{} has {} pet(s)".format(u123.first_name, len(pets)) | ||
for pet in pets: | ||
if pet is not chomp: | ||
pet.add_friend(chomp) | ||
print("{} is now friends with Chomp".format(pet.name))) | ||
try: | ||
foo = client.User.first(where={"username": "foo"}) | ||
except ItemNotFound: | ||
print("User 'foo' does not exist!") | ||
else: | ||
chomp.update(owner=foo) | ||
print("Chomp has been sold to {}".format(foo.name)) | ||
chomp.destroy() | ||
print("RIP, Chomp. You lived a happy life.") | ||
Installation | ||
============ | ||
|
||
To install ``potion-client``, run: | ||
|
||
:: | ||
|
||
pip install potion-client | ||
|
||
|
||
|
||
|
||
Authors | ||
======= | ||
|
||
Potion-client was written by `João Cardoso <https://github.com/joaocardoso>`_ and `Lars Schöning <https://github.com/lyschoening>`_. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# flake8: noqa | ||
from functools import partial | ||
from operator import getitem, delitem, setitem | ||
from six.moves.urllib.parse import urlparse, urljoin | ||
from weakref import WeakValueDictionary | ||
import collections | ||
import requests | ||
|
||
from .converter import PotionJSONDecoder, PotionJSONSchemaDecoder | ||
from .resource import Reference, Resource, uri_for | ||
from .links import Link | ||
from .utils import upper_camel_case, snake_case | ||
|
||
|
||
class Client(object): | ||
# TODO optional HTTP/2 support: this makes multiple queries simultaneously. | ||
|
||
def __init__(self, api_root_url, schema_path='/schema', fetch_schema=True, **session_kwargs): | ||
self._instances = WeakValueDictionary() | ||
self._resources = {} | ||
|
||
self.session = session = requests.Session() | ||
for key, value in session_kwargs.items(): | ||
setattr(session, key, value) | ||
|
||
parse_result = urlparse(api_root_url) | ||
self._root_url = '{}://{}'.format(parse_result.scheme, parse_result.netloc) | ||
self._api_root_url = api_root_url # '{}://{}'.format(parse_result.scheme, parse_result.netloc) | ||
self._root_path = parse_result.path | ||
self._schema_url = api_root_url + schema_path | ||
|
||
if fetch_schema: | ||
self._fetch_schema() | ||
|
||
def _fetch_schema(self): | ||
schema = self.session \ | ||
.get(self._schema_url) \ | ||
.json(cls=PotionJSONSchemaDecoder, | ||
referrer=self._schema_url, | ||
client=self) | ||
|
||
# NOTE these should perhaps be definitions in Flask-Potion | ||
for name, resource_schema in schema['properties'].items(): | ||
resource = self.resource_factory(name, resource_schema) | ||
setattr(self, upper_camel_case(name), resource) | ||
|
||
def instance(self, uri, cls=None, default=None, **kwargs): | ||
instance = self._instances.get(uri, None) | ||
|
||
if instance is None: | ||
if cls is None: | ||
try: | ||
cls = self._resources[uri[:uri.rfind('/')]] | ||
except KeyError: | ||
cls = Reference | ||
|
||
if isinstance(default, Resource) and default._uri is None: | ||
default._status = 200 | ||
default._uri = uri | ||
instance = default | ||
else: | ||
instance = cls(uri=uri, **kwargs) | ||
self._instances[uri] = instance | ||
return instance | ||
|
||
def fetch(self, uri, cls=PotionJSONDecoder, **kwargs): | ||
# TODO handle URL fragments (#properties/id etc.) | ||
response = self.session \ | ||
.get(urljoin(self._root_url, uri, True)) | ||
|
||
response.raise_for_status() | ||
|
||
return response.json(cls=cls, | ||
client=self, | ||
referrer=uri, | ||
**kwargs) | ||
|
||
def resource_factory(self, name, schema, resource_cls=None): | ||
""" | ||
Registers a new resource with a given schema. The schema must not have any unresolved references | ||
(such as `{"$ref": "#"}` for self-references, or otherwise). A subclass of :class:`Resource` | ||
may be provided to add specific functionality to the resulting :class:`Resource`. | ||
:param str name: | ||
:param dict schema: | ||
:param Resource resource_cls: a subclass of :class:`Resource` or None | ||
:return: The new :class:`Resource`. | ||
""" | ||
cls = type(str(upper_camel_case(name)), (resource_cls or Resource, collections.MutableMapping), { | ||
'__doc__': schema.get('description', '') | ||
}) | ||
|
||
cls._schema = schema | ||
cls._client = self | ||
cls._links = links = {} | ||
|
||
for link_schema in schema['links']: | ||
link = Link(self, | ||
rel=link_schema['rel'], | ||
href=link_schema['href'], | ||
method=link_schema['method'], | ||
schema=link_schema.get('schema', None), | ||
target_schema=link_schema.get('targetSchema', None)) | ||
|
||
# Set Resource._self, etc. for the special methods as they are managed by the Resource class | ||
if link.rel in ('self', 'instances', 'create', 'update', 'destroy'): | ||
setattr(cls, '_{}'.format(link.rel), link) | ||
links[link.rel] = link | ||
|
||
if link.rel != 'update': # 'update' is a special case because of MutableMapping.update() | ||
setattr(cls, snake_case(link.rel), link) | ||
|
||
# TODO routes (instance & non-instance) | ||
|
||
for property_name, property_schema in schema.get('properties', {}).items(): | ||
# skip $uri and $id as these are already implemented in Resource and overriding them causes unnecessary | ||
# fetches. | ||
if property_name.startswith('$'): | ||
continue | ||
|
||
if property_schema.get('readOnly', False): | ||
# TODO better error message. Raises AttributeError("can't set attribute") | ||
setattr(cls, | ||
property_name, | ||
property(fget=partial((lambda name, obj: getitem(obj, name)), property_name), | ||
doc=property_schema.get('description', None))) | ||
else: | ||
setattr(cls, | ||
property_name, | ||
property(fget=partial((lambda name, obj: getitem(obj, name)), property_name), | ||
fset=partial((lambda name, obj, value: setitem(obj, name, value)), property_name), | ||
fdel=partial((lambda name, obj: delitem(obj, name)), property_name), | ||
doc=property_schema.get('description', None))) | ||
|
||
root = None | ||
if 'instances' in links: | ||
root = cls._instances.href | ||
elif 'self' in links: | ||
root = cls._self.href[:cls._self.href.rfind('/')] | ||
else: | ||
root = self._root_path + '/' + name.replace('_', '-') | ||
|
||
self._resources[root] = cls | ||
return cls | ||
|
||
|
||
ASC = ASCENDING = False | ||
DESC = DESCENDING = True |
Oops, something went wrong.