Skip to content

Commit

Permalink
Merge
Browse files Browse the repository at this point in the history
  • Loading branch information
agrausem committed Dec 5, 2014
2 parents 33203f7 + c99c8fe commit 53345c8
Show file tree
Hide file tree
Showing 16 changed files with 257 additions and 151 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -39,3 +39,6 @@ htmlcov/
.mr.developer.cfg
.project
.pydevproject

# PyCharm
.idea
8 changes: 8 additions & 0 deletions README.rst
Expand Up @@ -16,6 +16,14 @@ This module requires a SPORE description of an API. Some descriptions for known
.. image:: https://coveralls.io/repos/agrausem/britney/badge.png?branch=master
:target: https://coveralls.io/r/agrausem/britney?branch=master

.. image:: https://pypip.in/v/britney/badge.png
:target: https://crate.io/packages/britney-http-signature/
:alt: Latest PyPI version

.. image:: https://pypip.in/d/britney/badge.png
:target: https://crate.io/packages/britney-http-signature/
:alt: Number of PyPI downloads




Expand Down
23 changes: 16 additions & 7 deletions britney/__init__.py
Expand Up @@ -10,14 +10,16 @@
This project is based on spyre
"""

import requests
from .core import Spore
import urllib
import json
from .errors import SporeMethodStatusError as HTTPError


def spyre(spec_uri, base_url=None):
def new(spec_uri, base_url=None):
"""
"""
from .core import Spore

if spec_uri.startswith('http'):
func = _new_from_url
else:
Expand All @@ -29,11 +31,10 @@ def spyre(spec_uri, base_url=None):

return Spore(**api_description)


def _new_from_file(spec_uri):
"""
"""
import json

with open(spec_uri, 'r') as spec_file:
spec = json.loads(spec_file.read())

Expand All @@ -43,5 +44,13 @@ def _new_from_file(spec_uri):
def _new_from_url(spec_uri):
"""
"""
response = requests.get(spec_uri)
return response.json()
try:
response = urllib.urlopen(spec_uri)
desc = response.read()
except AttributeError:
response = urllib.request.urlopen(spec_uri)
desc = response.read().decode()
return json.loads(desc)


spyre = new
129 changes: 68 additions & 61 deletions britney/core.py
Expand Up @@ -4,22 +4,25 @@
britney.core
~~~~~~~~~~~~
Core objects that builds the whole SPORE client, based on informations defined
in a SPORE description. See https://github.com/SPORE/specifications/blob/master/spore_description.pod for
more informations about SPORE descriptions
Core objects that builds the whole SPORE client, based on information defined
in a SPORE description.
See https://github.com/SPORE/specifications/blob/master/spore_description.pod
for more information about SPORE descriptions
"""

import requests
import requests.models
from requests.compat import is_py2
from requests.compat import urlparse

from . import errors
from .request import RequestBuilder
from .utils import get_user_agent


class Spore(object):
""" Base class generating at run-time the Spore HTTP Client to a REST
Environnment.
Environment.
:param name: name of the REST Web Service (required)
:param base_url: base url of the REST Web Service (required)
Expand All @@ -31,7 +34,7 @@ class Spore(object):
to None)
:param methods: a dict containing the methods information that will
instantiate a :py:class:`~britney.core.SporeMethod` (required)
:param meta: meta informations about the description and the service
:param meta: meta information about the description and the service
"""

def __new__(cls, *args, **kwargs):
Expand All @@ -41,12 +44,12 @@ def __new__(cls, *args, **kwargs):
spec_errors['name'] = 'A name for this client is required'

if not kwargs.get('base_url', ''):
spec_errors['base_url'] = 'A base URL to the REST Web Service is '
'required'
spec_errors['base_url'] = 'A base URL to the REST Web Service is \
required'

if not 'methods' in kwargs:
spec_errors['methods'] = 'One method is required to create the '
'client'
spec_errors['methods'] = 'One method is required to create the \
client'
else:
authentication = kwargs.get('authentication', None)
formats = kwargs.get('formats', None)
Expand All @@ -58,7 +61,7 @@ def __new__(cls, *args, **kwargs):
for method_name, method_description in kwargs['methods'].items():
try:
method = SporeMethod(
name=method_name,
name=method_name,
base_url=kwargs['base_url'],
middlewares=instance.middlewares,
global_authentication=authentication,
Expand All @@ -70,15 +73,15 @@ def __new__(cls, *args, **kwargs):
method_errors[method_name] = method_error
else:
setattr(instance, method_name, method)

if spec_errors or method_errors:
raise errors.SporeClientBuildError(spec_errors,
method_errors)
method_errors)

return instance

def __init__(self, name='', base_url='', authority='', formats=None,
version='', authentication=None, methods=None, meta=None):
def __init__(self, name='', base_url='', authority='', formats=None,
version='', authentication=None, methods=None, meta=None):
self.name = name
self.authority = authority
self.base_url = base_url
Expand Down Expand Up @@ -114,7 +117,6 @@ def enable_if(self, predicate, middleware, **kwargs):

self.middlewares.append((predicate, middleware(**kwargs)))


def add_default(self, param, value):
"""
"""
Expand All @@ -138,13 +140,13 @@ class SporeMethod(object):
:param path: the path to the wanted resource (eg: '/api/users') (required)
:param required_params: a list parameters that will be used to build the
url path (defaults to None)
:param optional_params: a list of parameters that wille be used to build
:param optional_params: a list of parameters that will be used to build
the request query
:param expected_status: a list of expected status for the reponse (defaults
:param expected_status: a list of expected status for the response (defaults
to None).
:param description: a short description for the method (defaults to '')
:param middlewares: a list of the middlewares that will be applied to the
request and the reponse. See :py:class:~`britney.middleware.BaseMiddleware`
request and the response. See :py:class:~`britney.middleware.BaseMiddleware`
:param authentication: boolean to set authentication on the method. This
param replaces the global authentication parameter set for the whole client
on this particular method (defaults to None)
Expand All @@ -161,30 +163,30 @@ class SporeMethod(object):

PAYLOAD_HTTP_METHODS = ('POST', 'PUT', 'PATCH')
HTTP_METHODS = PAYLOAD_HTTP_METHODS + ('GET', 'TRACE', 'OPTIONS', 'DELETE',
'HEAD')
'HEAD')

def __new__(cls, *args, **kwargs):
method_errors = {}

if not kwargs.get('method', ''):
method_errors['method'] = 'A method description should define the HTTP '
'Method to use'
method_errors['method'] = 'A method description should define the \
HTTP Method to use'

if not kwargs.get('path', ''):
method_errors['path'] = 'A method description should define the path to '
'the wanted resource(s)'
method_errors['path'] = 'A method description should define the \
path to the wanted resource(s)'

if not kwargs.get('name', ''):
method_errors['name'] = 'A method description should define a name'

if not kwargs.get('api_base_url', '') \
and not kwargs.get('base_url', ''):
method_errors['base_url'] = 'A method description should define a base '
'url if not defined for the whle client'
method_errors['base_url'] = 'A method description should define a \
base url if not defined for the whole client'

if method_errors:
raise errors.SporeMethodBuildError(method_errors)

documentation = kwargs.get('documentation', '')
description = kwargs.get('description', '')

Expand All @@ -193,11 +195,12 @@ def __new__(cls, *args, **kwargs):

return instance

def __init__(self, name='', api_base_url='', method='', path='',
required_params=None, optional_params=None, expected_status=None,
required_payload=False, description='', authentication=None,
formats=None, base_url='', documentation='', middlewares=None,
global_authentication=None, global_formats=None, defaults=None):
def __init__(self, name='', api_base_url='', method='', path='',
required_params=None, optional_params=None,
expected_status=None, required_payload=False, description='',
authentication=None, formats=None, base_url='',
documentation='', middlewares=None, global_authentication=None,
global_formats=None, defaults=None):

self.name = name
self.method = method
Expand Down Expand Up @@ -227,22 +230,22 @@ def __repr__(self):

def base_environ(self):
""" Builds the base environment dictionnary describing the request to
be sent to the REST Web Service. See https://github.com/SPORE/specifications/blob/master/spore_implementation.pod
for more informations about the keys defined here. You can also check
the WSGI environnment keys http://wsgi.readthedocs.org/en/latest/definitions.html
be sent to the REST Web Service.
See https://github.com/SPORE/specifications/blob/master/spore_implementation.pod
for more information about the keys defined here. You can also check
the WSGI environment keys http://wsgi.readthedocs.org/en/latest/definitions.html
"""

parsed_base_url = urlparse(self.base_url)

def script_name(path):
return path.rstrip('/')
def script_name(parsed_url):
return parsed_url.path.rstrip('/')

def userinfo(parsed_url):
if parsed_url.username is None:
return ''
return '{0.username}:{0.password}'.format(parsed_url)


def server_port(parsed_url):
if not parsed_url.port:
if parsed_url.scheme == 'http':
Expand All @@ -256,20 +259,21 @@ def server_port(parsed_url):
path_info, query_string = path
else:
path_info, query_string = path[0], ''

return {
'REQUEST_METHOD': self.method,
'SERVER_NAME': parsed_base_url.hostname,
'SERVER_PORT': server_port(parsed_base_url),
'SCRIPT_NAME': script_name(parsed_base_url.path),
'SERVER_PORT': server_port(parsed_base_url),
'SCRIPT_NAME': script_name(parsed_base_url),
'PATH_INFO': path_info,
'QUERY_STRING': query_string,
'HTTP_USER_AGENT': 'britney',
'HTTP_USER_AGENT': get_user_agent(),
'spore.expected_status': self.expected_status,
'spore.authentication': self.authentication,
'spore.params': '',
'spore.payload': '',
'spore.errors': '',
'spore.headers' : {},
'spore.format': self.formats,
'spore.userinfo': userinfo(parsed_base_url),
'wsgi.url_scheme': parsed_base_url.scheme,
Expand All @@ -279,24 +283,27 @@ def is_a_param(self, param):
return param in self.required_params or param in self.optional_params

def get_defaults(self):
return {
param: value for param, value in
(self.defaults.viewitems() if is_py2 else self.defaults.items())
if self.is_a_param(param)
} if self.defaults else {}

if self.defaults:
default_items = self.defaults.viewitems() if is_py2 \
else self.defaults.items()
return {
param: value for param, value in default_items
if self.is_a_param(param)
}
return {}

def build_payload(self, data):
"""
"""

if not data and self.required_payload:
raise errors.SporeMethodCallError('Payload is required for '
'this function')
'this function')

return data

def build_params(self, **kwargs):
""" Check aguments passed to call method and build the spore
""" Check arguments passed to call method and build the spore
parameters value
"""

Expand All @@ -305,25 +312,27 @@ def build_params(self, **kwargs):
passed_args = kwargs.viewkeys() if is_py2 else kwargs.keys()
if self.defaults:
default_args = self.get_defaults().viewkeys() if is_py2 \
else self.get_defaults().keys()
else self.get_defaults().keys()
all_args = set(passed_args).union(default_args)
else:
all_args = set(passed_args)

# nothing to do here
if not all_params and not all_args:
return []

# some required parameters are missing
if not req_params.issubset(all_args):
raise errors.SporeMethodCallError('Required parameters are missing',
expected=req_params - passed_args)

expected = req_params - passed_args
raise errors.SporeMethodCallError('Required parameters are missing',
expected=expected)

# too much arguments passed to func
if (all_args - all_params):
if all_args - all_params:
expected = passed_args - all_params
raise errors.SporeMethodCallError('Too much parameter',
expected=passed_args - all_params)
expected=expected)

kwargs.update(**self.get_defaults())
return list(kwargs.viewitems() if is_py2 else kwargs.items())

Expand All @@ -341,7 +350,6 @@ def check_status(self, response):
if status not in self.expected_status:
raise errors.SporeMethodStatusError(response)


def __call__(self, **kwargs):
""" Calls the method with required parameters
:raises: ~britney.errors.SporeMethodStatusError
Expand All @@ -357,7 +365,6 @@ def __call__(self, **kwargs):
'spore.params': self.build_params(**kwargs)
})


for predicate, middleware in self.middlewares:
if predicate(environ):
callback = middleware(environ)
Expand Down

0 comments on commit 53345c8

Please sign in to comment.