hammock
A good place to REST. Hammock provides a friendly approach to develop resources for a REST server. currently supports falcon.
General
Your REST server might have resources.
- Make a resources package somewhere in your project (with init.py and so on).
- Add your resources to that package (see below).
- Add the resources to the falcon API (see below).
- Use the auto-generated client.
- Use the auto-generated service API.
Creating a Resource
A resource defined by its URL prefix.
A resource is a class with name of its module, capitalized, and inherits from the hammock.Resource
. Resource class:
Lets create an helloworld resource, in resources/helloworld.py:
import hammock
class Helloworld(hammock.Resource):
@hammock.get()
def say(self):
return 'hello world'
This class definition will add a resource in the URL /helloworld
. The hammock.get
decorator
defines the say method as a rest method for GET /helloworld
.
Adding Resources Package to Falcon API
Simply use this code:
import hammock
from somewhere.in.your.project import resources
hammock.Hammock('falcon', resources)
Resources Package Hierarchy
The resources package hierarcy defines the REST server paths. For example, a package resources of the form:
resources/
__init__.py
books.py
class Books(hammock.Resource)
users/
__init__.py
authors.py
class Authors(hammock.Resource)
book_lenders.py
class BookLenders(hammock.Resource)
Will have the Books
resource class on URL /books
. The Authors
resource
class on url /users/authors
and BookLenders
resource class on URL
/users/book-lenders
.
- All the translation between names and URLs is automatic, but can be overrided.
- The route methods in a resource class will extend the class URL.
For example, within the
BookLenders
class, a method decorated withhammock.get('{lender_id}/last-book')
will add a GET method on an absoulute path/users/book-lenders/{lender_id}/last-book
.
REST Methods
As explained above, adding a rest method is done by adding a method to the resource class with an
appropriate decorator.
You can use one decorators: hammock.get
, hammock.post
, hammock.put
, hammock.patch
or hammock.delete
.
The developer may write a method that gets arguments, or keyword arguments, and returns
something, usually an object that can be converted to JSON format in the response body. The arguments
will be parsed automatically from the request URL query or JSON body (depending on the method used), and the return
value will be written to the response message.
The decorators may get some arguments:
- path (default: ""): representing a path in the resource. This path may include variables, surrounded by curly braces, same as you would have done in falcon.
- success_code (default: 200): the code that will be returned with the HTTP response, in case that no error was raised.
- result_content_type (default "application/json"): the content type that will be in the header of the response.
- rule_name: see Policy, below.
Route Argument Types
You may add types for the route method's arguments. The types are given in the docstring of the method and also influence the CLI. To document an argument, add line/lines to the docstring:
"""
:param [<optional-param-type>] <param-name>: <param-doc (multiline)>
"""
The you define an argument type, the argument will be converted to the proper type before entering your method. If an error occurs during the conversion, an HTTP 400 (Bad Request) will be raised. Special cases:
- If a type is
list
, and a single item will be given, the item will be converted to a list with one item. - The value
None
won't be converted and it will be passed as is. In the case of typelist
, an empty list will be passed to the method.
Special arguments
Naming the method's argument in a special way, might result in a different behaviour:
-
_headers
: the argument that will be passed to the method is the headers of the request. -
_file
: This method expects "application/octet-stream" as content-type of the request, and the stream will be delivered to the_file
argument. Notice that this method must be "PUT" or "POST". Other arguments will be passed through the URL query parameters. -
_list
: when JSON body is a list (and not a dict) the body will go to this variable.
URLs
The URL of your resource is created using the python packages and class name.
For example, if your Echo class is in your.project.resources.tools.echo.Echo
,
and you add the package your.project.resources
to the rest.Rest class, the resource URL will be:
/tools/echo
, since its class name is Echo and it is in subpackage tools.
Overriding URLs
- For packages: if you want the URL component of a package to differ from its name,
you can add to the package
__init__.py
file:PATH = 'some-other-name'
. This will replace the package name withsome-other-name
in the URL. - For classes: adding PATH class member
class SomeResource(hammock.Resource):
PATH = 'some-other-name'
Client Methods:
In case you want that one route method will be expand to different
methods in the generated client. you can use the route client_methods
argument, and map a method name to kwargs that will be enforced as part
of the client's request.
You can see this example.
Policy
Define routing policies using a policy JSON file. A policy rule is according to oslo.policy. To use the policy file, instantiate the Hammock instance with the policy_file keyword argument. A rule has a name and a Boolean expression that is evaluated using the headers and target resource parameters.
- The rule name is combined of rule-group and rule-name
- The rule group is by default the resource class name, lowercase,
and can be overridden using the
POLICY_GROUP_NAME
class member. Setting this member toFalse
will result in no policy enforcement on the class. - The rule name is the route method name, and can be overridden using the
rule_name
keyword argument in the route decorator. - The full name is
{rule-group}:{rule-name}
- The rule group is by default the resource class name, lowercase,
and can be overridden using the
- The headers are converted to a credentials dict, by default using the Credentials class, but can be customized using credentials_class parameter.
- The request is converted to a dict using hammock engine, and passed to oslo.policy as the target field.
- Evaluating the expression:
the expression is key:value tuple. The key might be:
-
rule
: then the target is reference to another rule. -
role
: then the value is looked up in a list stored in a key 'roles' in the credentials dict. - project_id/user_id/domain_id: the credential's project_id/user_id/domain_id.
- other: the key is searched in the credentials rules, and then the value is compared after evaluating the python expression: value % target
- reference.
- Example: rule is 'credentials_entry:%(target_entry)s', then if credentials are {'credentials_entry': 'x'} and target is {'target_entry': 'x'}, then the rule is evaluated to True.
-
- The expression might have and/or parentheses.
Enforcement in the route method context
Not all policies can be enforced in the route method entry level.
The credentials dict might be injected to the route method, using
the _credentials
keyword argument. For example:
import hammock
import manager
class MySecuredResource(hammock.Resource):
@hammock.get()
def get(self, _credentials, resource_id):
resource = manager.get(resource_id)
    if resource['project_id'] != _credentials.project_id:
   raise exceptions.Unauthorized('Get secured resource {}'.format(resource_id))
return vm
CLI
A CLI for a hammock auto-generated client can be initiated using The hammock.cli.app.App
class.
You can inherit it in your project, to override the prompt, description, version or proprties.
You initiate it using a list of hammock client classes. Notice that it expects classes and not instances. All the clients are merged into a one CLI.
Command names
The CLI is populated with commands and sub commands using the client Class. A Resource class in the client will be converted into a command, containing all its routing methods, or resource nested classes as sub commands (recursively). The name of the resource and routing methods will be conveted into a command name (no caps, dashes, etc...) automatically, but you can override this name as follows:
For resources package, add CLI_COMMAND_NAME
variable in the __init__.py
file of the package, for a resource class: add a CLI_COMMAND_NAME
attribute, for a route method, use the cli_command_name
parameter when you define it. The effect will be as follows:
-
None
will have no effect and the command name will be converted from the package/class/method name. -
False
will remove the package/class/method from the CLI (and all its nested dependencies). - any other string will be used as the command name.
Command arguments
-
The arguments for a command are taken from the route method it represents.
-
args
are converted to positional arguments.kwargs
are converted to optional arguments. -
Type and documentation strings are taken from the method doc string: if the doc string contains a line(s):
:param [<optional-param-type>] <param-name>: <param-help(multi-line)>
.- The type can be
str
,uuid
,bool
,int
offloat
to specify type. -
uuid
is a string in UUID formati (according to RFC 4122). -
list
will define a CLI argparsernargs='*'
. -
bool[False]
orbool[True]
will use theaction='store_true'
oraction='store_false'
respectively, and type bool.
- The type can be
-
Return value of a method can be defined using the doc string line(s):
:return <return-type>: <return-help(multi-line)>
, The return value affects the command as follows:-
dict
: means the return value is an item, the CLI will print a table with dict keys and values. The CLI will add option-f
that can change the output format (JSON, YAML, etc...). -
list
: means the return value is a list of items. The CLI will print a table of the values for each item, will add the-f
flag that can define the format,-c
to select specific columns and more. It is best practice to return a list of dicts, containing the same keys. If a list of other types is returned, the CLI will convert it to a list of dicts containing one keyvalue
. - other types will be printed to the stdout of the CLI.
-
-
The documentation string for the command is taken from the route method doc string.
Client Generation
In order to dump a hammock service python binding (a python client) into a file:
python -m hammock.client ClientClassName path.to.resource_package [http://default-url] > my-client.py
The client code json can also be obtained from a running hammock service:
curl http://your-server/_client
API Generation
In order to dump a hammock service API into a file:
python -m hammock.doc path.to.resource_package [--json|--yaml] > my-resource-api.yml
The API json can also be obtained from a running hammock service:
curl http://your-server/_api