py-rs 1.0 ConceptThis framework is designed to provide lightweight, robust and rapid RESTful service development in Python. Readers are assumed to be familiar with Python and REST. TerminologyTargetPython function defining HTTP resource and responsible for producing the response for HTTP Request. Targets specified by decorating Python functions with py-rs api decorators: @rs.get @rs.path('foo') def foo(): pass # foo is responsible for processing "GET /foo" request. Target has following requirements for parameters:
@rs.get @rs.path('/foo/{id}') def foo(id): pass # value of {id} will be passed into *foo* function as *id* value. @rs.get @rs.path('/bar') def bar(skip, take): pass # due to path has no parameters the values of skip and take parameters # will be looked up in HTTP Request URI query part: # 1. "GET /bar?skip=10&take=100" will be normally served by bar function. # 2. If any of the parameters will not be found than _py-rs_ will raise 400 Bad Request error. # 3. Unless default value for parameters will be provided within function signature e.g. (skip=0,take=0). @rs.post @rs.path('/baz') @rs.consumes('application/x-www-form-urlencoded') def baz(firstname, lastname, email, sex): pass # the values for baz parameters are expected to be the form values of HTTP Request "POST /baz".
@rs.get class Foo @rs.path('/foo') def foo(self): pass @rs.path('/bar') def bar(self): pass # requests "GET /foo" and "GET /bar" will invoke foo and bar methods with the same Foo instance.
@rs.post def foo(entity=rs.context.entity): pass # "POST /" entity will be passed into foo function as entity parameter. @rs.post def foo(request=rs.context.request): pass # "POST /" request will be passed into foo function as request parameter. @rs.post def foo(request=rs.context.ui): pass # "POST /" / URI(instance of rs.message.uri) will be passed into foo function as uri parameter. @rs.get @rs.path('/{class}') def foo(klass=rs.context.alias('class')): pass # "GET /{class}" value of rs.path class param will be associated with klass through 'class' alias. @rs.get @rs.path('/bar') def bar(from_=rs.context.alias('from'), length): pass # value of 'from' alias will be looked up in query params of "GET /bar" request, if not found 400 Bad Request # is returned. @rs.get @rs.path('/baz') def baz(from_=rs.context.alias('from', default='0'), length): pass # if default value for alias is specified than in case of not found 'from' parameter it will be used and # no error will be raised. Target has following requirements for return values:
return entity # returns entity, 200 OK will be chosen as default status code. return entity, 200 # returns 200 OK and entity. return entity, 201, {'Location':'/entity'} # returns 201 Created, entity and Location header.
return (entity, 200), # tuple (entity, 200) is considered as single response entity to return.
If Target needs to produce HTTP error code as HTTP Response rs.error instance with required status code should be raised: @rs.get def foo(): raise rs.error(400) # will return 400 Bad Request response. TypePython class representing the container of Target methods. Types specified by decorating Python classes with py-rs api decorators: @rs.path('foo') class Foo: @rs.get def foo(self): pass @rs.post def bar(self): pass @rs.put def baz(self): pass @rs.delete def qux(self): pass All py-rs api decorators of Type are inherited by its methods. Instances of Type for single HTTP Resource are considered to be singletons during the resource's lifetime. Type __init__ method signature should have been able to be invoked without parameters: @rs.path('foo') class Foo: def __init__(self): pass # or @rs.path('bar') class Bar: def __init__(self, logger=some_logger): pass # both classes can instantiate objects as Foo() and Bar() Inner Type classes supported: @rs.path('foo') class Foo: @rs.path('bar') class Bar: @rs.get def bar(self): pass # "GET /foo/bar" request will be processed by Foo.Bar.bar method. PathPath value used to identify Target upon which to apply HTTP Request. rs.path api decorator specifies the target's Path: @rs.get @rs.path('foo') def foo(): pass # associate foo function with /foo HTTP Request URI. To specify Path parameters use {name of parameter}-syntax: @rs.get @rs.path('foo/{id}' def foo(id): pass # {id} will be substituted by the part following foo/ of "GET /foo/*" requests. Regular expression syntax supported: @rs.post @rs.path('re(set|start|load)') def reset(): pass # HTTP Requests with URI like '/restart', '/reset', '/reload' all will be processed by reset function. Due to there can be some conflict situations with Path resolution the current implementation is using length of the Path value as a partial key to resolve ambiguous situations and applies the longest Path as the priority one unless Path value will contain zero number of capturing groups(considered as exact match): @rs.post @rs.path('re(set|start|load)') def reset(): pass @rs.post @rs.path('restart') def restart(): pass # "POST /restart" will be processed by restart function. If py-rs will not find Target with suitable Path for HTTP Request 404 Not Found will be returned. MethodMethod indicates the HTTP Method to be performed on Target identified by Path. Following api decorators define the target's Method:
@rs.get def get(): pass # serves "GET /" requests. @rs.post def post(): pass # serves "POST /" requests. @rs.put def put(): pass # serves "PUT /" requests. @rs.delete def delete(): pass # serves "DELETE /" requests. @rs.method('HEAD') def head(): pass # serves "HEAD /" requests. If py-rs will not find Target with suitable Method for HTTP Request 405 Method Not Allowed will be returned. ConsumerConsumer specifies a media type which Target should consume and an optional HTTP Request Entity processor. rs.consumes(media, consumer=None) api decorator is used to define Consumer. REQUIRED parameter media defines expected HTTP Request Content-Type header value. OPTIONAL parameter consumer specifies custom processor which has to be apply to the request's content before passing into Target: @rs.post @rs.consumes('application/json', json.loads) def foo(entity=rs.context.entity): pass # foo function will accept "POST /" request only if its Content-Type header matches consume media value - # 'application/json'. The value of entity parameter will be set to json.loads(request.entity) result. If the target does not define Consumer than it is assumed that Target consume any Content-Type. If no suitable Consumer found for Target the 415 Unsupported Media Type status code will be returned to HTTP Client. Consumer has following requirements for consumer parameter:
ProducerProducer specifies a media type which Target should produce and an optional HTTP Response Entity processor. rs.produces(media, producer=None) api decorator is used to define Producer. REQUIRED parameter media defines HTTP Response Content-Type header value. OPTIONAL parameter producer specifies custom processor which has to be apply to Target return entity before writing into HTTP Response: @rs.get @rs.produces('application/json', json.dumps) def foo(): return {'id':1, 'name':'Entity', 'zip':None} # foo function will respond to "GET /" request with json.dumps({'id':1, 'name':'Entity', 'zip':None}) result # as HTTP Response Entity. If the target does not define Producer but returns not None entity, the application\octet-stream mime-type will be used as default value for HTTP Response Content-Type header. Producer media value is being matched to media types in the request's Accept header during HTTP Request dispatching; if no suitable media will be found for Accept header the 406 Not Acceptable status code will be returned to HTTP Client. Producer has following requirements for producer parameter:
ApplicationWSGI Application that serves HTTP Request and provides HTTP Response to HTTP Client of REST service. py-rs provides pep-0333 compatible Application. rs.application(resources=None) is called to create Application instance. By default all Targets decorated with py-rs api will be included in rs.application() instance as HTTP Resources. To specify exact Targets to serve by Application pass them in resources parameter value, note if targets are specified under the Type, the class should be passed as a resource instead of class methods: import rs @rs.get def foo(): pass @rs.path('/bar') class Bar: @rs.get def bar(self): pass @rs.post def baz(self): pass application = rs.application() # create an application with foo, bar, baz targets. application = rs.application([Bar]) # create an application with bar and baz targets of Bar type. It is recommended to use an external WSGI Server to run Application in production environment. However for testing purposes Application can be launched by run method: application.run('localhost', 8001) # host an application at http://localhost:8001; wsgiref.simple_server.WSGIServer will serve an application. AcknowledgementsInspired by JAX-RS. |