Skip to content

Instantly share code, notes, and snippets.

@scott2b
Created December 11, 2012 03:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scott2b/4255770 to your computer and use it in GitHub Desktop.
Save scott2b/4255770 to your computer and use it in GitHub Desktop.
An approach to route and view configuration in Pyramid that provides resource-level versions with content-type specification and api-version convenience URLs
"""REST principles state that we should version at the resource level. Both the version and the content-type of a
resource should be specified in the Accept header. But many developers prefer an approach which contains API
versions in the URL.
The approach in this gist finds a happy medium. Resource level versioning is provided, with resources being
specified for both version and content-type. Convenience URLs for API version levels are also configured, with each
API version being a specific set of versioned resources.
vendor-specified content types are configured in the form of vnd.namespace.Resource+mimetype. Routes for each of
these are configured as well as generic routes for the appropriate content type with the resource version that is
considered current. Resource versions are orthogonal to api versions. The configuration format is intended to be
expressive and to provide a fine grain of control over resource versions, associated content-types, api versions,
and the relationships between resource versions and api versions.
"""
CURRENT_API_VERSION = '2'
RESOURCES = [
{
'vnd_base': 'vnd.example.MyResource',
'route': '/myresource',
'route_name': 'myresoure',
'resource_versions': {
('1',('GET','POST')): {
'view': 'myproj.views.MyResourceView',
'attr': 'get_1', # attr should be optional depending on if view is callable
'renderers': ['json', 'xml', 'templates/myresource.pt'],
'api_versions': ['1'],
'default_for': ['1']
},
('2',('GET',)): {
'view': 'myproj.views.MyResourceView',
'attr': 'get_2',
'renderers': ['json', 'xml', 'templates/myresource.pt'],
'api_versions': ['1','2'],
'default_for': ['2']
},
('2',('POST',)): {
'view': 'myproj.views.MyResourceView',
'attr': 'post_2',
'renderers': ['json', 'xml', 'templates/myresource.pt'],
'api_versions': ['1','2'],
'default_for': ['2']
},
}
},
]
def add_view(configurator, view, **kwargs):
configurator.add_view(view, **kwargs)
def config_resources(configurator):
routes = []
for r in RESOURCES:
route_name = r['route_name']
route = r['route']
if route_name not in routes:
configurator.add_route(route_name, route)
routes.append(route_name)
for k,cfg in r['resource_versions'].items():
version, method = k
for renderer in cfg['renderers']:
if renderer == 'json':
default_accept = 'application/json'
ext = 'json'
elif renderer == 'xml':
default_accept = 'text/xml'
ext = 'xml'
else:
default_accept = 'text/html'
ext = 'html'
# default for current api version
if CURRENT_API_VERSION in cfg['default_for']:
add_view(configurator, cfg['view'],
attr=cfg.get('attr'),
request_method=method,
route_name=route_name,
renderer=renderer,
accept=default_accept)
# versioned resource for main route
add_view(configurator, cfg['view'],
attr=cfg.get('attr'),
request_method = method,
route_name=route_name,
renderer=renderer,
accept='application/%s.%s+%s' % (
r['vnd_base'], version, ext))
# resources for api convenience routes
for api_v in cfg['api_versions']:
rname = 'api-%s-%s' % (api_v, route_name)
if rname not in routes:
configurator.add_route(rname, '/%s%s' % (api_v, route))
routes.append(rname)
if api_v in cfg['default_for']:
add_view(configurator, cfg['view'],
attr=cfg.get('attr'),
request_method=method,
route_name=rname,
renderer=renderer,
accept=default_accept)
add_view(configurator, cfg['view'],
attr=cfg.get('attr'),
request_method = method,
route_name=rname,
renderer=renderer,
accept='application/%s.%s+%s' % (
r['vnd_base'], version, ext))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment