[Python-modules-commits] [flask-restful] 01/02: Imported Upstream version 0.2.12
Alexandre Viau
reazem-guest at moszumanska.debian.org
Thu Oct 23 20:56:08 UTC 2014
This is an automated email from the git hooks/post-receive script.
reazem-guest pushed a commit to branch master
in repository flask-restful.
commit 1da20e54bf3e149db44dcdb57b146cd1989083b5
Author: aviau <alexandre.viau at savoirfairelinux.com>
Date: Thu Oct 23 16:24:42 2014 -0400
Imported Upstream version 0.2.12
---
Flask_RESTful.egg-info/PKG-INFO | 10 +
Flask_RESTful.egg-info/SOURCES.txt | 18 +
Flask_RESTful.egg-info/dependency_links.txt | 1 +
Flask_RESTful.egg-info/not-zip-safe | 1 +
Flask_RESTful.egg-info/requires.txt | 10 +
Flask_RESTful.egg-info/top_level.txt | 1 +
PKG-INFO | 10 +
flask_restful/__init__.py | 565 ++++++++++++++++++++++++++++
flask_restful/fields.py | 287 ++++++++++++++
flask_restful/paging.py | 29 ++
flask_restful/representations/__init__.py | 1 +
flask_restful/representations/json.py | 29 ++
flask_restful/reqparse.py | 217 +++++++++++
flask_restful/types.py | 223 +++++++++++
flask_restful/utils/__init__.py | 34 ++
flask_restful/utils/cors.py | 47 +++
flask_restful/utils/crypto.py | 29 ++
flask_restful/utils/ordereddict.py | 130 +++++++
setup.cfg | 5 +
setup.py | 29 ++
20 files changed, 1676 insertions(+)
diff --git a/Flask_RESTful.egg-info/PKG-INFO b/Flask_RESTful.egg-info/PKG-INFO
new file mode 100644
index 0000000..13e0a5b
--- /dev/null
+++ b/Flask_RESTful.egg-info/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: Flask-RESTful
+Version: 0.2.12
+Summary: Simple framework for creating REST APIs
+Home-page: https://www.github.com/twilio/flask-restful/
+Author: Twilio API Team
+Author-email: help at twilio.com
+License: UNKNOWN
+Description: UNKNOWN
+Platform: any
diff --git a/Flask_RESTful.egg-info/SOURCES.txt b/Flask_RESTful.egg-info/SOURCES.txt
new file mode 100644
index 0000000..ec75a33
--- /dev/null
+++ b/Flask_RESTful.egg-info/SOURCES.txt
@@ -0,0 +1,18 @@
+setup.py
+Flask_RESTful.egg-info/PKG-INFO
+Flask_RESTful.egg-info/SOURCES.txt
+Flask_RESTful.egg-info/dependency_links.txt
+Flask_RESTful.egg-info/not-zip-safe
+Flask_RESTful.egg-info/requires.txt
+Flask_RESTful.egg-info/top_level.txt
+flask_restful/__init__.py
+flask_restful/fields.py
+flask_restful/paging.py
+flask_restful/reqparse.py
+flask_restful/types.py
+flask_restful/representations/__init__.py
+flask_restful/representations/json.py
+flask_restful/utils/__init__.py
+flask_restful/utils/cors.py
+flask_restful/utils/crypto.py
+flask_restful/utils/ordereddict.py
\ No newline at end of file
diff --git a/Flask_RESTful.egg-info/dependency_links.txt b/Flask_RESTful.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Flask_RESTful.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/Flask_RESTful.egg-info/not-zip-safe b/Flask_RESTful.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Flask_RESTful.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/Flask_RESTful.egg-info/requires.txt b/Flask_RESTful.egg-info/requires.txt
new file mode 100644
index 0000000..97f3c64
--- /dev/null
+++ b/Flask_RESTful.egg-info/requires.txt
@@ -0,0 +1,10 @@
+aniso8601>=0.82
+Flask>=0.8
+six>=1.3.0
+pytz
+
+[docs]
+sphinx
+
+[paging]
+pycrypto>=2.6
\ No newline at end of file
diff --git a/Flask_RESTful.egg-info/top_level.txt b/Flask_RESTful.egg-info/top_level.txt
new file mode 100644
index 0000000..f7b8527
--- /dev/null
+++ b/Flask_RESTful.egg-info/top_level.txt
@@ -0,0 +1 @@
+flask_restful
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..13e0a5b
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: Flask-RESTful
+Version: 0.2.12
+Summary: Simple framework for creating REST APIs
+Home-page: https://www.github.com/twilio/flask-restful/
+Author: Twilio API Team
+Author-email: help at twilio.com
+License: UNKNOWN
+Description: UNKNOWN
+Platform: any
diff --git a/flask_restful/__init__.py b/flask_restful/__init__.py
new file mode 100644
index 0000000..4debd43
--- /dev/null
+++ b/flask_restful/__init__.py
@@ -0,0 +1,565 @@
+from __future__ import absolute_import
+import difflib
+from functools import wraps, partial
+import re
+from flask import request, url_for, current_app
+from flask import abort as original_flask_abort
+from flask.views import MethodView
+from flask.signals import got_request_exception
+from werkzeug.exceptions import HTTPException, MethodNotAllowed, NotFound
+from werkzeug.http import HTTP_STATUS_CODES
+from werkzeug.wrappers import Response as ResponseBase
+from flask.ext.restful.utils import error_data, unpack
+from flask.ext.restful.representations.json import output_json
+import sys
+from flask.helpers import _endpoint_from_view_func
+from types import MethodType
+
+try:
+ #noinspection PyUnresolvedReferences
+ from collections import OrderedDict
+except ImportError:
+ from .utils.ordereddict import OrderedDict
+
+__all__ = ('Api', 'Resource', 'marshal', 'marshal_with', 'abort')
+
+
+def abort(http_status_code, **kwargs):
+ """Raise a HTTPException for the given http_status_code. Attach any keyword
+ arguments to the exception for later processing.
+ """
+ #noinspection PyUnresolvedReferences
+ try:
+ original_flask_abort(http_status_code)
+ except HTTPException as e:
+ if len(kwargs):
+ e.data = kwargs
+ raise e
+
+DEFAULT_REPRESENTATIONS = {'application/json': output_json}
+
+
+class Api(object):
+ """
+ The main entry point for the application.
+ You need to initialize it with a Flask Application: ::
+
+ >>> app = Flask(__name__)
+ >>> api = restful.Api(app)
+
+ Alternatively, you can use :meth:`init_app` to set the Flask application
+ after it has been constructed.
+
+ :param app: the Flask application object
+ :type app: flask.Flask
+ :param prefix: Prefix all routes with a value, eg v1 or 2010-04-01
+ :type prefix: str
+ :param default_mediatype: The default media type to return
+ :type default_mediatype: str
+ :param decorators: Decorators to attach to every resource
+ :type decorators: list
+ :param catch_all_404s: Use :meth:`handle_error`
+ to handle 404 errors throughout your app
+ :param url_part_order: A string that controls the order that the pieces
+ of the url are concatenated when the full url is constructed. 'b'
+ is the blueprint (or blueprint registration) prefix, 'a' is the api
+ prefix, and 'e' is the path component the endpoint is added with
+ :type catch_all_404s: bool
+
+ """
+
+ def __init__(self, app=None, prefix='',
+ default_mediatype='application/json', decorators=None,
+ catch_all_404s=False, url_part_order='bae'):
+ self.representations = dict(DEFAULT_REPRESENTATIONS)
+ self.urls = {}
+ self.prefix = prefix
+ self.default_mediatype = default_mediatype
+ self.decorators = decorators if decorators else []
+ self.catch_all_404s = catch_all_404s
+ self.url_part_order = url_part_order
+ self.blueprint_setup = None
+ self.endpoints = set()
+ self.resources = []
+ self.app = None
+
+ if app is not None:
+ self.app = app
+ self.init_app(app)
+
+ def init_app(self, app):
+ """Initialize this class with the given :class:`flask.Flask`
+ application or :class:`flask.Blueprint` object.
+
+ :param app: the Flask application or blueprint object
+ :type app: flask.Flask
+ :type app: flask.Blueprint
+
+ Examples::
+
+ api = Api()
+ api.init_app(app)
+ api.add_resource(...)
+
+ """
+ self.blueprint = None
+ # If app is a blueprint, defer the initialization
+ try:
+ app.record(self._deferred_blueprint_init)
+ # Flask.Blueprint has a 'record' attribute, Flask.Api does not
+ except AttributeError:
+ self._init_app(app)
+ else:
+ self.blueprint = app
+
+ def _complete_url(self, url_part, registration_prefix):
+ """This method is used to defer the construction of the final url in
+ the case that the Api is created with a Blueprint.
+
+ :param url_part: The part of the url the endpoint is registered with
+ :param registration_prefix: The part of the url contributed by the
+ blueprint. Generally speaking, BlueprintSetupState.url_prefix
+ """
+
+ parts = {'b' : registration_prefix,
+ 'a' : self.prefix,
+ 'e' : url_part}
+ return ''.join(parts[key] for key in self.url_part_order if parts[key])
+
+ @staticmethod
+ def _blueprint_setup_add_url_rule_patch(blueprint_setup, rule, endpoint=None, view_func=None, **options):
+ """Method used to patch BlueprintSetupState.add_url_rule for setup
+ state instance corresponding to this Api instance. Exists primarily
+ to enable _complete_url's function.
+
+ :param blueprint_setup: The BlueprintSetupState instance (self)
+ :param rule: A string or callable that takes a string and returns a
+ string(_complete_url) that is the url rule for the endpoint
+ being registered
+ :param endpoint: See BlueprintSetupState.add_url_rule
+ :param view_func: See BlueprintSetupState.add_url_rule
+ :param **options: See BlueprintSetupState.add_url_rule
+ """
+
+ if callable(rule):
+ rule = rule(blueprint_setup.url_prefix)
+ elif blueprint_setup.url_prefix:
+ rule = blueprint_setup.url_prefix + rule
+ options.setdefault('subdomain', blueprint_setup.subdomain)
+ if endpoint is None:
+ endpoint = _endpoint_from_view_func(view_func)
+ defaults = blueprint_setup.url_defaults
+ if 'defaults' in options:
+ defaults = dict(defaults, **options.pop('defaults'))
+ blueprint_setup.app.add_url_rule(rule, '%s.%s' % (blueprint_setup.blueprint.name, endpoint),
+ view_func, defaults=defaults, **options)
+
+ def _deferred_blueprint_init(self, setup_state):
+ """Synchronize prefix between blueprint/api and registration options, then
+ perform initialization with setup_state.app :class:`flask.Flask` object.
+ When a :class:`flask_restful.Api` object is initialized with a blueprint,
+ this method is recorded on the blueprint to be run when the blueprint is later
+ registered to a :class:`flask.Flask` object. This method also monkeypatches
+ BlueprintSetupState.add_url_rule with _blueprint_setup_add_url_rule_patch.
+
+ :param setup_state: The setup state object passed to deferred functions
+ during blueprint registration
+ :type setup_state: flask.blueprints.BlueprintSetupState
+
+ """
+
+ self.blueprint_setup = setup_state
+ if setup_state.add_url_rule.__name__ != '_blueprint_setup_add_url_rule_patch':
+ setup_state._original_add_url_rule = setup_state.add_url_rule
+ setup_state.add_url_rule = MethodType(Api._blueprint_setup_add_url_rule_patch,
+ setup_state)
+ if not setup_state.first_registration:
+ raise ValueError('flask-restful blueprints can only be registered once.')
+ self._init_app(setup_state.app)
+
+ def _init_app(self, app):
+ """Perform initialization actions with the given :class:`flask.Flask`
+ object.
+
+ :param app: The flask application object
+ :type app: flask.Flask
+ """
+ app.handle_exception = partial(self.error_router, app.handle_exception)
+ app.handle_user_exception = partial(self.error_router, app.handle_user_exception)
+
+ if len(self.resources) > 0:
+ for resource, urls, kwargs in self.resources:
+ self._register_view(app, resource, *urls, **kwargs)
+
+ def owns_endpoint(self, endpoint):
+ """Tests if an endpoint name (not path) belongs to this Api. Takes
+ in to account the Blueprint name part of the endpoint name.
+
+ :param endpoint: The name of the endpoint being checked
+ :return: bool
+ """
+
+ if self.blueprint:
+ if endpoint.startswith(self.blueprint.name):
+ endpoint = endpoint.split(self.blueprint.name + '.', 1)[-1]
+ else:
+ return False
+ return endpoint in self.endpoints
+
+ def _should_use_fr_error_handler(self):
+ """ Determine if error should be handled with FR or default Flask
+
+ The goal is to return Flask error handlers for non-FR-related routes,
+ and FR errors (with the correct media type) for FR endpoints. This
+ method currently handles 404 and 405 errors.
+
+ :return: bool
+ """
+ adapter = current_app.create_url_adapter(request)
+
+ try:
+ adapter.match()
+ except MethodNotAllowed as e:
+ # Check if the other HTTP methods at this url would hit the Api
+ valid_route_method = e.valid_methods[0]
+ rule, _ = adapter.match(method=valid_route_method, return_rule=True)
+ return self.owns_endpoint(rule.endpoint)
+ except NotFound:
+ return self.catch_all_404s
+ except:
+ # Werkzeug throws other kinds of exceptions, such as Redirect
+ pass
+
+
+ def _has_fr_route(self):
+ """Encapsulating the rules for whether the request was to a Flask endpoint"""
+ # 404's, 405's, which might not have a url_rule
+ if self._should_use_fr_error_handler():
+ return True
+ # for all other errors, just check if FR dispatched the route
+ if not request.url_rule:
+ return False
+ return self.owns_endpoint(request.url_rule.endpoint)
+
+ def error_router(self, original_handler, e):
+ """This function decides whether the error occured in a flask-restful
+ endpoint or not. If it happened in a flask-restful endpoint, our
+ handler will be dispatched. If it happened in an unrelated view, the
+ app's original error handler will be dispatched.
+
+ :param original_handler: the original Flask error handler for the app
+ :type original_handler: function
+ :param e: the exception raised while handling the request
+ :type e: Exception
+
+ """
+ if self._has_fr_route():
+ return self.handle_error(e)
+ return original_handler(e)
+
+ def handle_error(self, e):
+ """Error handler for the API transforms a raised exception into a Flask
+ response, with the appropriate HTTP status code and body.
+
+ :param e: the raised Exception object
+ :type e: Exception
+
+ """
+ got_request_exception.send(current_app._get_current_object(), exception=e)
+
+ if not hasattr(e, 'code') and current_app.propagate_exceptions:
+ exc_type, exc_value, tb = sys.exc_info()
+ if exc_value is e:
+ raise
+ else:
+ raise e
+
+ code = getattr(e, 'code', 500)
+ data = getattr(e, 'data', error_data(code))
+
+ if code >= 500:
+
+ # There's currently a bug in Python3 that disallows calling
+ # logging.exception() when an exception hasn't actually be raised
+ if sys.exc_info() == (None, None, None):
+ current_app.logger.error("Internal Error")
+ else:
+ current_app.logger.exception("Internal Error")
+
+ help_on_404 = current_app.config.get("ERROR_404_HELP", True)
+ if code == 404 and help_on_404 and ('message' not in data or
+ data['message'] == HTTP_STATUS_CODES[404]):
+ rules = dict([(re.sub('(<.*>)', '', rule.rule), rule.rule)
+ for rule in current_app.url_map.iter_rules()])
+ close_matches = difflib.get_close_matches(request.path, rules.keys())
+ if close_matches:
+ # If we already have a message, add punctuation and continue it.
+ if "message" in data:
+ data["message"] += ". "
+ else:
+ data["message"] = ""
+
+ data['message'] += 'You have requested this URI [' + request.path + \
+ '] but did you mean ' + \
+ ' or '.join((rules[match]
+ for match in close_matches)) + ' ?'
+
+ resp = self.make_response(data, code)
+
+ if code == 401:
+ resp = self.unauthorized(resp)
+
+ return resp
+
+ def mediatypes_method(self):
+ """Return a method that returns a list of mediatypes
+ """
+ return lambda resource_cls: self.mediatypes() + [self.default_mediatype]
+
+ def add_resource(self, resource, *urls, **kwargs):
+ """Adds a resource to the api.
+
+ :param resource: the class name of your resource
+ :type resource: :class:`Resource`
+ :param urls: one or more url routes to match for the resource, standard
+ flask routing rules apply. Any url variables will be
+ passed to the resource method as args.
+ :type urls: str
+
+ :param endpoint: endpoint name (defaults to :meth:`Resource.__name__.lower`
+ Can be used to reference this route in :class:`fields.Url` fields
+ :type endpoint: str
+
+ Additional keyword arguments not specified above will be passed as-is
+ to :meth:`flask.Flask.add_url_rule`.
+
+ Examples::
+
+ api.add_resource(HelloWorld, '/', '/hello')
+ api.add_resource(Foo, '/foo', endpoint="foo")
+ api.add_resource(FooSpecial, '/special/foo', endpoint="foo")
+
+ """
+ if self.app is not None:
+ self._register_view(self.app, resource, *urls, **kwargs)
+ else:
+ self.resources.append((resource, urls, kwargs))
+
+
+ def _register_view(self, app, resource, *urls, **kwargs):
+ endpoint = kwargs.pop('endpoint', None) or resource.__name__.lower()
+ self.endpoints.add(endpoint)
+
+ if endpoint in app.view_functions.keys():
+ previous_view_class = app.view_functions[endpoint].__dict__['view_class']
+
+ # if you override the endpoint with a different class, avoid the collision by raising an exception
+ if previous_view_class != resource:
+ raise ValueError('This endpoint (%s) is already set to the class %s.' % (endpoint, previous_view_class.__name__))
+
+ resource.mediatypes = self.mediatypes_method() # Hacky
+ resource.endpoint = endpoint
+ resource_func = self.output(resource.as_view(endpoint))
+
+ for decorator in self.decorators:
+ resource_func = decorator(resource_func)
+
+
+ for url in urls:
+ # If this Api has a blueprint
+ if self.blueprint:
+ # And this Api has been setup
+ if self.blueprint_setup:
+ # Set the rule to a string directly, as the blueprint is already
+ # set up.
+ rule = self._complete_url(url, self.blueprint_setup.url_prefix)
+ else:
+ # Set the rule to a function that expects the blueprint prefix
+ # to construct the final url. Allows deferment of url finalization
+ # in the case that the associated Blueprint has not yet been
+ # registered to an application, so we can wait for the registration
+ # prefix
+ rule = partial(self._complete_url, url)
+ else:
+ # If we've got no Blueprint, just build a url with no prefix
+ rule = self._complete_url(url, '')
+ # Add the url to the application or blueprint
+ app.add_url_rule(rule, view_func=resource_func, **kwargs)
+
+ def output(self, resource):
+ """Wraps a resource (as a flask view function), for cases where the
+ resource does not directly return a response object
+
+ :param resource: The resource as a flask view function
+ """
+ @wraps(resource)
+ def wrapper(*args, **kwargs):
+ resp = resource(*args, **kwargs)
+ if isinstance(resp, ResponseBase): # There may be a better way to test
+ return resp
+ data, code, headers = unpack(resp)
+ return self.make_response(data, code, headers=headers)
+ return wrapper
+
+ def url_for(self, resource, **values):
+ """Generates a URL to the given resource."""
+ return url_for(resource.endpoint, **values)
+
+ def make_response(self, data, *args, **kwargs):
+ """Looks up the representation transformer for the requested media
+ type, invoking the transformer to create a response object. This
+ defaults to (application/json) if no transformer is found for the
+ requested mediatype.
+
+ :param data: Python object containing response data to be transformed
+ """
+ for mediatype in self.mediatypes() + [self.default_mediatype]:
+ if mediatype in self.representations:
+ resp = self.representations[mediatype](data, *args, **kwargs)
+ resp.headers['Content-Type'] = mediatype
+ return resp
+
+ def mediatypes(self):
+ """Returns a list of requested mediatypes sent in the Accept header"""
+ return [h for h, q in request.accept_mimetypes]
+
+ def representation(self, mediatype):
+ """Allows additional representation transformers to be declared for the
+ api. Transformers are functions that must be decorated with this
+ method, passing the mediatype the transformer represents. Three
+ arguments are passed to the transformer:
+
+ * The data to be represented in the response body
+ * The http status code
+ * A dictionary of headers
+
+ The transformer should convert the data appropriately for the mediatype
+ and return a Flask response object.
+
+ Ex::
+
+ @api.representation('application/xml')
+ def xml(data, code, headers):
+ resp = make_response(convert_data_to_xml(data), code)
+ resp.headers.extend(headers)
+ return resp
+ """
+ def wrapper(func):
+ self.representations[mediatype] = func
+ return func
+ return wrapper
+
+ def unauthorized(self, response):
+ """ Given a response, change it to ask for credentials """
+
+ realm = current_app.config.get("HTTP_BASIC_AUTH_REALM", "flask-restful")
+ challenge = u"{0} realm=\"{1}\"".format("Basic", realm)
+
+ response.headers['WWW-Authenticate'] = challenge
+ return response
+
+
+class Resource(MethodView):
+ """
+ Represents an abstract RESTful resource. Concrete resources should
+ extend from this class and expose methods for each supported HTTP
+ method. If a resource is invoked with an unsupported HTTP method,
+ the API will return a response with status 405 Method Not Allowed.
+ Otherwise the appropriate method is called and passed all arguments
+ from the url rule used when adding the resource to an Api instance. See
+ :meth:`~flask.ext.restful.Api.add_resource` for details.
+ """
+ representations = None
+ method_decorators = []
+
+ def dispatch_request(self, *args, **kwargs):
+
+ # Taken from flask
+ #noinspection PyUnresolvedReferences
+ meth = getattr(self, request.method.lower(), None)
+ if meth is None and request.method == 'HEAD':
+ meth = getattr(self, 'get', None)
+ assert meth is not None, 'Unimplemented method %r' % request.method
+
+ for decorator in self.method_decorators:
+ meth = decorator(meth)
+
+ resp = meth(*args, **kwargs)
+
+ if isinstance(resp, ResponseBase): # There may be a better way to test
+ return resp
+
+ representations = self.representations or {}
+
+ #noinspection PyUnresolvedReferences
+ for mediatype in self.mediatypes():
+ if mediatype in representations:
+ data, code, headers = unpack(resp)
+ resp = representations[mediatype](data, code, headers)
+ resp.headers['Content-Type'] = mediatype
+ return resp
+
+ return resp
+
+
+def marshal(data, fields):
+ """Takes raw data (in the form of a dict, list, object) and a dict of
+ fields to output and filters the data based on those fields.
+
+ :param fields: a dict of whose keys will make up the final serialized
+ response output
+ :param data: the actual object(s) from which the fields are taken from
+
+
+ >>> from flask.ext.restful import fields, marshal
+ >>> data = { 'a': 100, 'b': 'foo' }
+ >>> mfields = { 'a': fields.Raw }
+
+ >>> marshal(data, mfields)
+ OrderedDict([('a', 100)])
+
+ """
+ def make(cls):
+ if isinstance(cls, type):
+ return cls()
+ return cls
+
+ if isinstance(data, (list, tuple)):
+ return [marshal(d, fields) for d in data]
+
+ items = ((k, marshal(data, v) if isinstance(v, dict)
+ else make(v).output(k, data))
+ for k, v in fields.items())
+ return OrderedDict(items)
+
+
+class marshal_with(object):
+ """A decorator that apply marshalling to the return values of your methods.
+
+ >>> from flask.ext.restful import fields, marshal_with
+ >>> mfields = { 'a': fields.Raw }
+ >>> @marshal_with(mfields)
+ ... def get():
+ ... return { 'a': 100, 'b': 'foo' }
+ ...
+ ...
+ >>> get()
+ OrderedDict([('a', 100)])
+
+ see :meth:`flask.ext.restful.marshal`
+ """
+ def __init__(self, fields):
+ """:param fields: a dict of whose keys will make up the final
+ serialized response output"""
+ self.fields = fields
+
+ def __call__(self, f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ resp = f(*args, **kwargs)
+ if isinstance(resp, tuple):
+ data, code, headers = unpack(resp)
+ return marshal(data, self.fields), code, headers
+ else:
+ return marshal(resp, self.fields)
+ return wrapper
diff --git a/flask_restful/fields.py b/flask_restful/fields.py
new file mode 100644
index 0000000..93978b7
--- /dev/null
+++ b/flask_restful/fields.py
@@ -0,0 +1,287 @@
+from decimal import Decimal as MyDecimal, ROUND_HALF_EVEN
+import six
+try:
+ from urlparse import urlparse, urlunparse
+except ImportError:
+ # python3
+ from urllib.parse import urlparse, urlunparse
+
+from flask_restful import types, marshal
+from flask import url_for
+
+__all__ = ["String", "FormattedString", "Url", "DateTime", "Float",
+ "Integer", "Arbitrary", "Nested", "List", "Raw", "Boolean",
+ "Fixed", "Price"]
+
+
+class MarshallingException(Exception):
+ """
+ This is an encapsulating Exception in case of marshalling error.
+ """
+
+ def __init__(self, underlying_exception):
+ # just put the contextual representation of the error to hint on what
+ # went wrong without exposing internals
+ super(MarshallingException, self).__init__(six.text_type(underlying_exception))
+
+
+def is_indexable_but_not_string(obj):
+ return not hasattr(obj, "strip") and hasattr(obj, "__iter__")
+
+
+def get_value(key, obj, default=None):
+ """Helper for pulling a keyed value off various types of objects"""
+ if type(key) == int:
+ return _get_value_for_key(key, obj, default)
+ else:
+ return _get_value_for_keys(key.split('.'), obj, default)
+
+
+def _get_value_for_keys(keys, obj, default):
+ if len(keys) == 1:
+ return _get_value_for_key(keys[0], obj, default)
+ else:
+ return _get_value_for_keys(
+ keys[1:], _get_value_for_key(keys[0], obj, default), default)
+
+
+def _get_value_for_key(key, obj, default):
+ if is_indexable_but_not_string(obj):
+ try:
+ return obj[key]
+ except (IndexError, TypeError, KeyError):
+ pass
+ return getattr(obj, key, default)
+
+
+def to_marshallable_type(obj):
+ """Helper for converting an object to a dictionary only if it is not
+ dictionary already or an indexable object nor a simple type"""
+ if obj is None:
+ return None # make it idempotent for None
+
+ if hasattr(obj, '__getitem__'):
+ return obj # it is indexable it is ok
+
+ if hasattr(obj, '__marshallable__'):
+ return obj.__marshallable__()
+
+ return dict(obj.__dict__)
+
+
+class Raw(object):
+ """Raw provides a base field class from which others should extend. It
+ applies no formatting by default, and should only be used in cases where
+ data does not need to be formatted before being serialized. Fields should
+ throw a MarshallingException in case of parsing problem.
+ """
+
+ def __init__(self, default=None, attribute=None):
+ self.attribute = attribute
+ self.default = default
+
+ def format(self, value):
+ """Formats a field's value. No-op by default, concrete fields should
+ override this and apply the appropriate formatting.
+
+ :param value: The value to format
+ :exception MarshallingException: In case of formatting problem
+
+ Ex::
+
+ class TitleCase(Raw):
+ def format(self, value):
+ return unicode(value).title()
+ """
+ return value
+
+ def output(self, key, obj):
+ """Pulls the value for the given key from the object, applies the
+ field's formatting and returns the result.
+ :exception MarshallingException: In case of formatting problem
+ """
+
+ value = get_value(key if self.attribute is None else self.attribute, obj)
+
+ if value is None:
+ return self.default
+
+ return self.format(value)
+
+
+class Nested(Raw):
+ """Allows you to nest one set of fields inside another.
+ See :ref:`nested-field` for more information
+
+ :param dict nested: The dictionary to nest
+ :param bool allow_null: Whether to return None instead of a dictionary
+ with null keys, if a nested dictionary has all-null keys
+ """
+
+ def __init__(self, nested, allow_null=False, **kwargs):
+ self.nested = nested
+ self.allow_null = allow_null
+ super(Nested, self).__init__(**kwargs)
+
+ def output(self, key, obj):
+ value = get_value(key if self.attribute is None else self.attribute, obj)
+ if self.allow_null and value is None:
+ return None
+
+ return marshal(value, self.nested)
+
+
+class List(Raw):
+ def __init__(self, cls_or_instance, **kwargs):
+ super(List, self).__init__(**kwargs)
+ error_msg = ("The type of the list elements must be a subclass of "
+ "flask_restful.fields.Raw")
+ if isinstance(cls_or_instance, type):
+ if not issubclass(cls_or_instance, Raw):
+ raise MarshallingException(error_msg)
+ self.container = cls_or_instance()
+ else:
+ if not isinstance(cls_or_instance, Raw):
+ raise MarshallingException(error_msg)
+ self.container = cls_or_instance
+
+ def output(self, key, data):
+ value = get_value(key if self.attribute is None else self.attribute, data)
+ # we cannot really test for external dict behavior
+ if is_indexable_but_not_string(value) and not isinstance(value, dict):
+ # Convert all instances in typed list to container type
+ if isinstance(value, set):
+ value = list(value)
+
+ return [
+ self.container.output(idx,
+ val if isinstance(val, dict) and
+ not isinstance(self.container, Nested)
+ and not type(self.container) is Raw
+ else value)
+ for idx, val in enumerate(value)
+ ]
+
+ if value is None:
+ return self.default
+
+ return [marshal(value, self.container.nested)]
+
+
+class String(Raw):
+ def format(self, value):
+ try:
+ return six.text_type(value)
+ except ValueError as ve:
+ raise MarshallingException(ve)
+
+
+class Integer(Raw):
+ """ Field for outputting an integer value.
+
+ :param int default: The default value for the field, if no value is
+ specified.
+
+ :param attribute: If the public facing value differs from the internal
+ value, use this to retrieve a different attribute from the response
+ than the publicly named value.
+ """
+ def __init__(self, default=0, attribute=None):
+ super(Integer, self).__init__(default, attribute)
+
+ def format(self, value):
+ try:
+ if value is None:
+ return self.default
+ return int(value)
+ except ValueError as ve:
+ raise MarshallingException(ve)
+
+
+class Boolean(Raw):
+ def format(self, value):
+ return bool(value)
+
+
+class FormattedString(Raw):
+ def __init__(self, src_str):
+ super(FormattedString, self).__init__()
+ self.src_str = six.text_type(src_str)
+
+ def output(self, key, obj):
+ try:
+ data = to_marshallable_type(obj)
+ return self.src_str.format(**data)
+ except (TypeError, IndexError) as error:
+ raise MarshallingException(error)
+
+
+class Url(Raw):
+ """
+ A string representation of a Url
+ """
+ def __init__(self, endpoint, absolute=False, scheme=None):
+ super(Url, self).__init__()
+ self.endpoint = endpoint
+ self.absolute = absolute
+ self.scheme = scheme
+
+ def output(self, key, obj):
+ try:
+ data = to_marshallable_type(obj)
+ o = urlparse(url_for(self.endpoint, _external = self.absolute, **data))
+ if self.absolute:
+ scheme = self.scheme if self.scheme is not None else o.scheme
+ return urlunparse((scheme, o.netloc, o.path, "", "", ""))
+ return urlunparse(("", "", o.path, "", "", ""))
+ except TypeError as te:
+ raise MarshallingException(te)
+
+
+class Float(Raw):
+ """
+ A double as IEEE-754 double precision.
+ ex : 3.141592653589793 3.1415926535897933e-06 3.141592653589793e+24 nan inf -inf
+ """
+
+ def format(self, value):
+ try:
+ return repr(float(value))
+ except ValueError as ve:
+ raise MarshallingException(ve)
+
+
+class Arbitrary(Raw):
+ """
+ A floating point number with an arbitrary precision
+ ex: 634271127864378216478362784632784678324.23432
+ """
+
+ def format(self, value):
+ return six.text_type(MyDecimal(value))
+
+
+class DateTime(Raw):
+ """Return a RFC822-formatted datetime string in UTC"""
+
+ def format(self, value):
+ try:
+ return types.rfc822(value)
+ except AttributeError as ae:
+ raise MarshallingException(ae)
+
+ZERO = MyDecimal()
+
+
+class Fixed(Raw):
+ def __init__(self, decimals=5, **kwargs):
+ super(Fixed, self).__init__(**kwargs)
+ self.precision = MyDecimal('0.' + '0' * (decimals - 1) + '1')
+
+ def format(self, value):
+ dvalue = MyDecimal(value)
+ if not dvalue.is_normal() and dvalue != ZERO:
+ raise MarshallingException('Invalid Fixed precision number.')
+ return six.text_type(dvalue.quantize(self.precision, rounding=ROUND_HALF_EVEN))
+
+Price = Fixed
diff --git a/flask_restful/paging.py b/flask_restful/paging.py
new file mode 100644
index 0000000..57d267f
--- /dev/null
+++ b/flask_restful/paging.py
@@ -0,0 +1,29 @@
+from flask_restful.utils.crypto import decrypt, encrypt
+DEFAULT_PAGE_SIZE = 50
+
+
+def retrieve_next_page(key, seed, args, callback, initial_bookmark=None):
+ """
+ A helper for the bookmark pager.
... 826 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/flask-restful.git
More information about the Python-modules-commits
mailing list