[Python-modules-commits] [mimerender] 01/03: New upstream version 0.6.0
Dominik George
natureshadow-guest at moszumanska.debian.org
Thu Sep 22 12:27:40 UTC 2016
This is an automated email from the git hooks/post-receive script.
natureshadow-guest pushed a commit to branch master
in repository mimerender.
commit 2cd7ea2dbc6b8f38a778722b253efc20772eb27c
Author: Dominik George <nik at naturalnet.de>
Date: Thu Sep 22 14:05:10 2016 +0200
New upstream version 0.6.0
---
PKG-INFO | 25 ++
setup.cfg | 5 +
setup.py | 36 +++
src/mimerender.egg-info/PKG-INFO | 25 ++
src/mimerender.egg-info/SOURCES.txt | 7 +
src/mimerender.egg-info/dependency_links.txt | 1 +
src/mimerender.egg-info/requires.txt | 1 +
src/mimerender.egg-info/top_level.txt | 1 +
src/mimerender.py | 464 +++++++++++++++++++++++++++
9 files changed, 565 insertions(+)
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..7f1a667
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,25 @@
+Metadata-Version: 1.1
+Name: mimerender
+Version: 0.6.0
+Summary: RESTful HTTP Content Negotiation for Flask, Bottle, web.py and webapp2 (Google App Engine)
+Home-page: https://github.com/martinblech/mimerender
+Author: Martin Blech
+Author-email: martinblech at gmail.com
+License: MIT
+Description: This module provides a decorator that wraps a HTTP
+ request handler to select the correct render function for a given HTTP
+ Accept header. It uses mimeparse to parse the accept string and select the
+ best available representation. Supports Flask, Bottle, web.py and webapp2
+ out of the box, and it's easy to add support for other frameworks.
+Platform: all
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Requires: python_mimeparse (>=0.1.4)
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..81ce5a6
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+from setuptools import setup
+
+setup(
+ name='mimerender',
+ version='0.6.0',
+ description='RESTful HTTP Content Negotiation for Flask, Bottle, web.py '
+ 'and webapp2 (Google App Engine)',
+ author='Martin Blech',
+ author_email='martinblech at gmail.com',
+ url='https://github.com/martinblech/mimerender',
+ license='MIT',
+ long_description="""This module provides a decorator that wraps a HTTP
+ request handler to select the correct render function for a given HTTP
+ Accept header. It uses mimeparse to parse the accept string and select the
+ best available representation. Supports Flask, Bottle, web.py and webapp2
+ out of the box, and it's easy to add support for other frameworks.""",
+ platforms=['all'],
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ ],
+ py_modules=['mimerender'],
+ package_dir={'':'src'},
+ requires=['python_mimeparse (>=0.1.4)'],
+ install_requires=['python_mimeparse >= 0.1.4'],
+)
diff --git a/src/mimerender.egg-info/PKG-INFO b/src/mimerender.egg-info/PKG-INFO
new file mode 100644
index 0000000..7f1a667
--- /dev/null
+++ b/src/mimerender.egg-info/PKG-INFO
@@ -0,0 +1,25 @@
+Metadata-Version: 1.1
+Name: mimerender
+Version: 0.6.0
+Summary: RESTful HTTP Content Negotiation for Flask, Bottle, web.py and webapp2 (Google App Engine)
+Home-page: https://github.com/martinblech/mimerender
+Author: Martin Blech
+Author-email: martinblech at gmail.com
+License: MIT
+Description: This module provides a decorator that wraps a HTTP
+ request handler to select the correct render function for a given HTTP
+ Accept header. It uses mimeparse to parse the accept string and select the
+ best available representation. Supports Flask, Bottle, web.py and webapp2
+ out of the box, and it's easy to add support for other frameworks.
+Platform: all
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Requires: python_mimeparse (>=0.1.4)
diff --git a/src/mimerender.egg-info/SOURCES.txt b/src/mimerender.egg-info/SOURCES.txt
new file mode 100644
index 0000000..4e65ee8
--- /dev/null
+++ b/src/mimerender.egg-info/SOURCES.txt
@@ -0,0 +1,7 @@
+setup.py
+src/mimerender.py
+src/mimerender.egg-info/PKG-INFO
+src/mimerender.egg-info/SOURCES.txt
+src/mimerender.egg-info/dependency_links.txt
+src/mimerender.egg-info/requires.txt
+src/mimerender.egg-info/top_level.txt
\ No newline at end of file
diff --git a/src/mimerender.egg-info/dependency_links.txt b/src/mimerender.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/mimerender.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/src/mimerender.egg-info/requires.txt b/src/mimerender.egg-info/requires.txt
new file mode 100644
index 0000000..11e9367
--- /dev/null
+++ b/src/mimerender.egg-info/requires.txt
@@ -0,0 +1 @@
+python_mimeparse >= 0.1.4
\ No newline at end of file
diff --git a/src/mimerender.egg-info/top_level.txt b/src/mimerender.egg-info/top_level.txt
new file mode 100644
index 0000000..3b89566
--- /dev/null
+++ b/src/mimerender.egg-info/top_level.txt
@@ -0,0 +1 @@
+mimerender
diff --git a/src/mimerender.py b/src/mimerender.py
new file mode 100644
index 0000000..46337df
--- /dev/null
+++ b/src/mimerender.py
@@ -0,0 +1,464 @@
+"""
+RESTful resource variant selection using the HTTP Accept header.
+"""
+
+__version__ = '0.6.0'
+__author__ = 'Martin Blech <martinblech at gmail.com>'
+__license__ = 'MIT'
+
+import mimeparse
+from functools import wraps
+import re
+
+class MimeRenderException(Exception): pass
+
+XML = 'xml'
+JSON = 'json'
+JSONLD = 'jsonld'
+JSONP = 'jsonp'
+BSON = 'bson'
+YAML = 'yaml'
+XHTML = 'xhtml'
+HTML = 'html'
+TXT = 'txt'
+CSV = 'csv'
+TSV = 'tsv'
+RSS = 'rss'
+RDF = 'rdf'
+ATOM = 'atom'
+M3U = 'm3u'
+PLS = 'pls'
+XSPF = 'xspf'
+ICAL = 'ical'
+KML = 'kml'
+KMZ = 'kmz'
+MSGPACK = 'msgpack'
+
+# Map of mime categories to specific mime types. The first mime type in each
+# category's tuple is the default one (e.g. the default for XML is
+# application/xml).
+_MIME_TYPES = {
+ XML: ('text/xml', 'application/xml', 'application/x-xml'),
+ JSON: ('application/json',),
+ JSONLD: ('application/ld+json',),
+ JSONP: ('application/javascript',),
+ BSON: ('application/bson',),
+ YAML: ('application/x-yaml', 'text/yaml',),
+ XHTML: ('application/xhtml+xml',),
+ HTML: ('text/html',),
+ TXT: ('text/plain',),
+ CSV: ('text/csv',),
+ TSV: ('text/tab-separated-values',),
+ RSS: ('application/rss+xml',),
+ RDF: ('application/rdf+xml',),
+ ATOM: ('application/atom+xml',),
+ M3U: ('audio/x-mpegurl', 'application/x-winamp-playlist', 'audio/mpeg-url', 'audio/mpegurl',),
+ PLS: ('audio/x-scpls',),
+ XSPF: ('application/xspf+xml',),
+ ICAL: ('text/calendar',),
+ KML: ('application/vnd.google-earth.kml+xml',),
+ KMZ: ('application/vnd.google-earth.kmz',),
+ MSGPACK: ('application/x-msgpack',),
+}
+
+def register_mime(shortname, mime_types):
+ """
+ Register a new mime type.
+ Usage example:
+ mimerender.register_mime('svg', ('application/x-svg', 'application/svg+xml',))
+ After this you can do:
+ @mimerender.mimerender(svg=render_svg)
+ def GET(...
+ ...
+ """
+ if shortname in _MIME_TYPES:
+ raise MimeRenderException('"%s" has already been registered'%shortname)
+ _MIME_TYPES[shortname] = mime_types
+
+def _get_mime_types(shortname):
+ try:
+ return _MIME_TYPES[shortname]
+ except KeyError:
+ raise MimeRenderException('No known mime types for "%s"'%shortname)
+
+def _get_short_mime(mime):
+ for shortmime, mimes in _MIME_TYPES.items():
+ if mime in mimes:
+ return shortmime
+ raise MimeRenderException('No short mime for type "%s"' % mime)
+
+def _best_mime(supported, accept_string=None):
+ if accept_string is None:
+ return None
+ return mimeparse.best_match(supported, accept_string)
+
+VARY_SEPARATOR = re.compile(r',\s*')
+def _fix_headers(headers, content_type):
+ fixed_headers = []
+ found_vary = False
+ found_content_type = False
+ for k, v in headers:
+ if k.lower() == 'vary':
+ found_vary = True
+ if 'accept' not in VARY_SEPARATOR.split(v.strip().lower()):
+ v = v + ',Accept'
+ if k.lower() == 'content-type':
+ found_content_type = True
+ fixed_headers.append((k, v))
+ if not found_vary:
+ fixed_headers.append(('Vary', 'Accept'))
+ if not found_content_type:
+ fixed_headers.append(('Content-Type', content_type))
+ return fixed_headers
+
+class MimeRenderBase(object):
+
+ def __init__(self, global_default=None, global_override_arg_idx=None,
+ global_override_input_key=None, global_charset=None,
+ global_not_acceptable_callback=None):
+ self.global_default = global_default
+ self.global_override_arg_idx = global_override_arg_idx
+ self.global_override_input_key = global_override_input_key
+ self.global_charset = global_charset
+ self.global_not_acceptable_callback = global_not_acceptable_callback
+
+ def __call__(self, default=None, override_arg_idx=None,
+ override_input_key=None, charset=None,
+ not_acceptable_callback=None,
+ **renderers):
+ """
+ Main mimerender decorator. Usage::
+
+ @mimerender(default='xml', override_arg_idx=-1, override_input_key='format', , <renderers>)
+ GET(self, ...) (or POST, etc.)
+
+ The decorated function must return a dict with the objects necessary to
+ render the final result to the user. The selected renderer will be
+ called with the dict contents as keyword arguments.
+ If override_arg_idx isn't None, the wrapped function's positional
+ argument at that index will be used instead of the Accept header.
+ override_input_key works the same way, but with web.input().
+
+ Example::
+
+ @mimerender(
+ default = 'xml',
+ override_arg_idx = -1,
+ override_input_key = 'format',
+ xhtml = xhtml_templates.greet,
+ html = xhtml_templates.greet,
+ xml = xml_templates.greet,
+ json = json_render,
+ yaml = json_render,
+ txt = json_render,
+ )
+ def greet(self, param):
+ message = 'Hello, %s!'%param
+ return {'message':message}
+
+ """
+ if not renderers:
+ raise ValueError('need at least one renderer')
+
+ def get_renderer(mime):
+ try:
+ return renderer_dict[mime]
+ except KeyError:
+ raise MimeRenderException('No renderer for mime "%s"'%mime)
+
+ if not default: default = self.global_default
+ if not override_arg_idx:
+ override_arg_idx = self.global_override_arg_idx
+ if not override_input_key:
+ override_input_key = self.global_override_input_key
+ if not charset: charset = self.global_charset
+ if not not_acceptable_callback:
+ not_acceptable_callback = self.global_not_acceptable_callback
+
+ supported = list()
+ renderer_dict = dict()
+ for shortname, renderer in renderers.items():
+ for mime in _get_mime_types(shortname):
+ supported.append(mime)
+ renderer_dict[mime] = renderer
+ if default:
+ default_mimes = _get_mime_types(default)
+ # default mime types should be last in the supported list
+ # (which means highest priority to mimeparse)
+ for mime in reversed(default_mimes):
+ supported.remove(mime)
+ supported.append(mime)
+ default_mime = default_mimes[0]
+ default_renderer = get_renderer(default_mime)
+ else:
+ # pick the first mime category from the `renderers` dict (note:
+ # this is only deterministic if len(`renderers`) == 1) and the
+ # default mime type/renderer for a given mime category.
+ default_mime = _get_mime_types(next(iter(renderers.keys())))[0]
+ default_renderer = renderer_dict[default_mime]
+
+ def wrap(target):
+ @wraps(target)
+ def wrapper(*args, **kwargs):
+ self.target_args = args
+ self.target_kwargs = kwargs
+ mime = None
+ shortmime = None
+ if override_arg_idx != None:
+ shortmime = args[override_arg_idx]
+ if not shortmime and override_input_key:
+ shortmime = self._get_request_parameter(override_input_key)
+ if shortmime: mime = _get_mime_types(shortmime)[0]
+ accept_header = self._get_accept_header()
+ if not mime:
+ if accept_header:
+ try:
+ mime = _best_mime(supported, accept_header)
+ except mimeparse.MimeTypeParseException:
+ return self._make_response('Invalid Accept header requested',
+ (('Content-Type',
+ 'text/plain'),),
+ '400 Bad Request')
+ else:
+ mime = default_mime
+ if mime:
+ renderer = get_renderer(mime)
+ else:
+ if not_acceptable_callback:
+ content_type, entity = not_acceptable_callback(
+ accept_header, supported)
+ return self._make_response(entity,
+ (('Content-Type',
+ content_type),),
+ '406 Not Acceptable')
+ else:
+ mime, renderer = default_mime, default_renderer
+ if not shortmime: shortmime = _get_short_mime(mime)
+ context_vars = dict(
+ mimerender_shortmime=shortmime,
+ mimerender_mime=mime,
+ mimerender_renderer=renderer)
+ for key, value in context_vars.items():
+ self._set_context_var(key, value)
+ try:
+ result = target(*args, **kwargs)
+ finally:
+ for key in context_vars.keys():
+ self._clear_context_var(key)
+ content_type = mime
+ if charset: content_type += '; charset=%s' % charset
+ headers = ()
+ status = '200 OK'
+ if isinstance(result, tuple):
+ if len(result) == 3:
+ result, status, headers = result
+ try:
+ headers = headers.items()
+ except AttributeError:
+ pass
+ elif len(result) == 2:
+ result, status = result
+ elif len(result) == 1:
+ (result,) = result
+ else:
+ raise ValueError()
+ content = renderer(**result)
+ headers = _fix_headers(headers, content_type)
+ return self._make_response(content, headers, status)
+ if hasattr(wrapper, '__wrapped__'):
+ # Workaround for new @wraps behavior in Python 3.4.
+ # Prevents `TypeError: () got an unexpected keyword argument`
+ # as reported in issue #25
+ del wrapper.__wrapped__
+ return wrapper
+
+ return wrap
+
+ def map_exceptions(self, mapping, *args, **kwargs):
+ """
+ Exception mapping helper decorator. Takes the same arguments as the
+ main decorator, plus `mapping`, which is a list of
+ `(exception_class, status_line)` pairs.
+ """
+ @self.__call__(*args, **kwargs)
+ def helper(e, status):
+ return dict(exception=e), status
+
+ def wrap(target):
+ @wraps(target)
+ def wrapper(*args, **kwargs):
+ try:
+ return target(*args, **kwargs)
+ except BaseException as e:
+ for klass, status in mapping:
+ if isinstance(e, klass):
+ return helper(e, status)
+ raise
+ return wrapper
+ return wrap
+
+ def _get_request_parameter(self, key, default=None):
+ return default
+
+ def _get_accept_header(self, default=None):
+ return default
+
+ def _set_context_var(self, key, value):
+ pass
+
+ def _clear_context_var(self, key):
+ pass
+
+ def _make_response(self, content, headers, status):
+ return content
+
+# web.py implementation
+try:
+ import web
+ class WebPyMimeRender(MimeRenderBase):
+ def _get_request_parameter(self, key, default=None):
+ return web.input().get(key, default)
+
+ def _get_accept_header(self, default=None):
+ return web.ctx.env.get('HTTP_ACCEPT', default)
+
+ def _set_context_var(self, key, value):
+ web.ctx[key] = value
+
+ def _clear_context_var(self, key):
+ del web.ctx[key]
+
+ def _make_response(self, content, headers, status):
+ web.ctx.status = status
+ for k, v in headers:
+ web.header(k, v)
+ return content
+
+except ImportError:
+ pass
+
+# Flask implementation
+try:
+ import flask
+ class FlaskMimeRender(MimeRenderBase):
+ def _get_request_parameter(self, key, default=None):
+ return flask.request.values.get(key, default)
+
+ def _get_accept_header(self, default=None):
+ return flask.request.headers.get('Accept', default)
+
+ def _set_context_var(self, key, value):
+ flask.request.environ[key] = value
+
+ def _clear_context_var(self, key):
+ del flask.request.environ[key]
+
+ def _make_response(self, content, headers, status):
+ return flask.make_response(content, status, headers)
+
+except ImportError:
+ pass
+
+# Bottle implementation
+try:
+ import bottle
+ class BottleMimeRender(MimeRenderBase):
+ def _get_request_parameter(self, key, default=None):
+ return bottle.request.params.get(key, default)
+
+ def _get_accept_header(self, default=None):
+ return bottle.request.headers.get('Accept', default)
+
+ def _set_context_var(self, key, value):
+ bottle.request.environ[key] = value
+
+ def _clear_context_var(self, key):
+ del bottle.request.environ[key]
+
+ def _make_response(self, content, headers, status):
+ bottle.response.status = status
+ for k, v in headers:
+ bottle.response.headers[k] = v
+ return content
+
+except ImportError:
+ pass
+
+# webapp2 implementation
+try:
+ import webapp2
+ class Webapp2MimeRender(MimeRenderBase):
+ def _get_request_parameter(self, key, default=None):
+ return webapp2.get_request().get(key, default_value=default)
+
+ def _get_accept_header(self, default=None):
+ return webapp2.get_request().headers.get('Accept', default)
+
+ def _set_context_var(self, key, value):
+ setattr(webapp2.get_request(), key, value)
+
+ def _clear_context_var(self, key):
+ delattr(webapp2.get_request(), key)
+
+ def _make_response(self, content, headers, status):
+ response = webapp2.get_request().response
+ response.status = status
+ for k, v in headers:
+ response.headers[k] = v
+ response.write(content)
+
+except ImportError:
+ pass
+
+def wsgi_wrap(app):
+ '''
+ Wraps a standard wsgi application e.g.:
+ def app(environ, start_response)
+ It intercepts the start_response callback and grabs the results from it
+ so it can return the status, headers, and body as a tuple
+ '''
+ @wraps(app)
+ def wrapped(environ, start_response):
+ status_headers = [None, None]
+ def _start_response(status, headers):
+ status_headers[:] = [status, headers]
+ body = app(environ, _start_response)
+ ret = body, status_headers[0], status_headers[1]
+ return ret
+ return wrapped
+
+class _WSGIMimeRender(MimeRenderBase):
+ def _get_request_parameter(self, key, default=None):
+ environ, start_response = self.target_args
+ return environ.get(key, default)
+
+ def _get_accept_header(self, default=None):
+ environ, start_response = self.target_args
+ return environ.get('HTTP_ACCEPT', default)
+
+ def _set_context_var(self, key, value):
+ environ, start_response = self.target_args
+ environ[key] = value
+
+ def _clear_context_var(self, key):
+ environ, start_response = self.target_args
+ del environ[key]
+
+ def _make_response(self, content, headers, status):
+ environ, start_response = self.target_args
+ start_response(status, headers)
+ return content
+
+
+def WSGIMimeRender(*args, **kwargs):
+ '''
+ A wrapper for _WSGIMimeRender that wrapps the
+ inner callable with wsgi_wrap first.
+ '''
+ def wrapper(*args2, **kwargs2):
+ # take the function
+ def wrapped(f):
+ return _WSGIMimeRender(*args, **kwargs)(*args2, **kwargs2)(wsgi_wrap(f))
+ return wrapped
+ return wrapper
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/mimerender.git
More information about the Python-modules-commits
mailing list