Created
December 11, 2012 03:53
-
-
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
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
"""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