Package: aversion
AVersion is a version selection application for WSGI stacks built with
PasteDeploy.  It allows multiple versions of a given application to be
addressed via URI or content type parameter.How AVersion WorksAVersion is a composite application, similar to the Pasteurlmap.
It is configured with several different applications–each of which
represents a different version of the desired end application.  It can
also be configured with a special application which handles
unversioned API requests, e.g., by returning a list of the available
versions.  AVersion then selects the appropriate application to pass a
given request to, based on the URI prefix, or a version parameter on
the content type or types specified in the “Content-Type” or “Accept”
headers.  In addition, AVersion can determine the best content type
for the reply, based on the URI suffix (e.g., “.json” could map to
content type “application/json”) or the “Accept” header.Configuring AVersionThe first step in configuring AVersion is to set up the section of the
Paste INI configuration file:[composite:main]
use = egg:aversion#aversionThe next step is to specify the recognized versions and their
associated applications.  The default application–the one called if
no version can be determined–is specified by theversionkey.
The specific versions are then specified by prefixing a version
specification withversion.; e.g., if you call the second version
of your application “v2”, you would specifyversion.v2.  The value
of any of these keys is a Paste application.  If you have a
“vers_list” application and “api_v1” and “api_v2” as the two versions
of your API, this configuration would look like:version = vers_list
version.v1 = api_v1
version.v2 = api_v2

[app:vers_list]
...

[app:api_v1]
...

[app:api_v2]
...This declares the available versions, but we have not provided any
criteria to select the version to route a request to.  We will
consider a simple URI mapping first; these options are declared by
prefixing the URI prefix withuri., and the value will be one of
the declared version identifiers.  For example, let us say that the
URI “/v1” will map to the “v1” API, while “/v2” maps to the “v2” API;
the relevant configuration would then be:uri./v1 = v1
uri./v2 = v2Note that these URI prefixes will be normalized, e.g., “//v1//”
normalizes to “/v1”.  Also, AVersion takes care to ensure that the
longest match will be used; if one of your URIs was “/v1.1” and the
other was “/v1”, a request to “/v1.1/foo” would be routed to the
first.  Finally, the prefixes are assumed to be complete path
fragments; the configuration shown above would not route a request to
“/v2-foo” to the “v2” application, while “/v2/foo” would be so routed.Some applications also need to allow specifying the API version
through a parameter on the content type.  For instance, if the
“Content-Type” header on a request body is set to
“application/json;version=2”, we want to select the “v2” API when the
request is made against “/”.  (The version determined from the URI
trumps any version determined from “Content-Type” or “Accept”.)
Similarly, if the “Accept” header includes
“application/json;version=2”, and the version cannot be determined
from the URI prefix or the “Content-Type” header, then we want to use
the “version” parameter on that selected content type.To configure the recognized content types, and to set up rules that
allow selection of the correct version, declare the types as
configuration keys prefixed withtype., e.g.,type.application/json.  The value of this configuration key can
then declare the version with a simple text substitution, e.g.:type.application/json = version:"v%(version)s"The text substitution should result in the name of the version, as
declared above.  It is also possible to alter the type, e.g., if a
given content type actually maps to another.  Consider, for instance:type.application/vnd.fooapp = type:"application/%(fmt)s"
    version:"v%(version)s"In this example, the content type
“application/vnd.fooapp;fmt=json;version=2” would make a call to the
“v2” API, with the “Accept” header rewritten to select
“application/json”.Both the “type” and “version” tokens are optional in thetype.configuration values.  When the “type” token is omitted, the existing
content type is used, and when the “version” token is omitted, no
version determination is made.  Do note, however, that the
“Content-Type” header of the response will likely be that appearing in
the “type” token; future work may be done to correct this.Since thetype.keys can overwrite the content types specified in
the “Accept” header, there is one more optional type of key that can
select the content type based on the URI suffix.  For instance, the
application may desire that, if the “.json” suffix is present, the
selected content type should be “application/json”.  To configure
this, simply use the suffix as a configuration key; the value will be
the desired content type:.json = application/jsonFinally, thetype.keys may select a version other than the one
which is desired.  For instance, the two API versions “v1.1” and
“v2”–appearing as a parameter to a content type–may identify the
same version of the API.  To enable this, use thealias.keys,
like so:alias.v1.1 = v2In this example, the content type
“application/vnd.fooapp;fmt=json;version=1.1” would also make a call
to the “v2” API.Although the above description ofalias.references content types,
aliasing also works for URIs, e.g.:uri./v1.1 = v1.1Here, accesses to the “/v1.1” endpoint will also be passed to the “v2”
api.Putting this all together, a complete AVersion configuration may look
like the following:[composite:main]
use = egg:aversion#aversion

# Specify the version applications
version = vers_list
version.v1 = api_v1
version.v2 = api_v2

# Specify an alias
alias.v1.1 = v2

# Map the URI prefixes
uri./v1 = v1
uri./v1.1 = v1.1
uri./v2 = v2

# Recognize several types
type.application/json = version:"v%(version)s"
type.application/xml = version:"v%(version)s"
type.application/vnd.fooapp = type:"application/%(fmt)s"
    version:"v%(version)s"

# Also recognize URI suffixes
.json = application/json
.xml = application/xml

[app:vers_list]
# Specify the vers_list application
...

[app:api_v1]
# Specify the v1 API application
...

[app:api_v2]
# Specify the v2 API application
...Extending AVersionAVersion processes a given request first for the URI prefixes and
suffixes, then for a version specified by the “Content-Type” header on
the request body, then for a version and content type set through the
“Accept” header (for which it implements the HTTP best-match content
type algorithm).  The first content type and version found in this
processing will be used.It is possible to extend theaversion.AVersionclass to alter the
order of these processing steps, or to provide other processing
steps.  The key is to override the_process()method.  This method
takes one required argument–the request object–and one optional
“result” argument, and returns the result.  (If the result argument is
not provided,_process()allocates an instance ofaversion.Result.)  It calls each of_proc_uri(),_proc_ctype_header(), and_proc_accept_header()in turn.Developers may also be interested in some of the available utility
functions, which are used by AVersion.  Thequoted_split()function can handle splitting multi-valued headers, like the “Accept”
header, even in the face of quoted arguments possibly containing the
separator.  Theparse_ctype()function takes a content type,
complete with its parameters, and returns the bare content type and a
dictionary containing those parameters.  Finally,best_match()implements the best-match algorithm for content types, and may be
useful as an example for implementing matchers for other “Accept-*”
headers.Advanced AVersion UsageAVersion adds several variables to the WSGI environment that may be
useful to applications.  The added WSGI environment variables all
begin withaversion.and are described below.aversion.versionTheaversion.versionvariable contains the name of the selected
version.  If the default application is selected, this value will beNone.  Otherwise, it will be a string identifying the configured
version.aversion.configTheaversion.configvariable contains a dictionary of three
entries: “versions”, “aliases”, and “types”.  Each of these entries
contains a dictionary which contains further information about the
configured components, as described below.versionsTheversionselement of theaversion.configvariable is
keyed by version names.  Each version is described by a dictionary
of three or four entries: thenamekey contains the name of
the version;appis a reference to the WSGI application
implementing that API version;paramsis a dictionary
containing version parameters (seeAdvanced AVersion
Configuration); andprefixes, if present, contains a list of
configured URI prefixes for that version.aliasesThealiaseselement of theaversion.configvariable is
keyed by aliases.  Each alias is described by a dictionary of
three entries: thealiaskey contains the name of the alias;
theversionkey contains the canonical version name
corresponding to the alias; andparamsis a dictionary
containing alias parameters (seeAdvanced AVersion
Configuration).typesThetypeselement of theaversion.configvariable is keyed
by content types.  Each content type is described by a dictionary
of two or three entries: thenamekey contains the name of the
content type; theparamskey is a dictionary containing
content type parameters (seeAdvanced AVersion Configuration);
andsuffixes, if present, contains a list of configured URI
suffixes for that type.Examples ofaversion.configWhat follows is an example of the value ofaversion.config, as it
would appear if the above example configuration was used; note thatparamsis an empty dictionary in all cases (Advanced AVersion
Configurationcovers parameters for versions, aliases, and content
types in more detail):{
    'versions': {
        'v1': {
            'name': 'v1',
            'app': <Python callable>,
            'params': {},
            'prefixes': ['/v1'],
        },
        'v2': {
            'name': 'v2',
            'app': <Python callable>,
            'params': {},
            'prefixes': ['/v2'],
        },
    },
    'aliases': {
        'v1.1': {
            'alias': 'v1.1',
            'version': 'v2',
            'params': {},
        },
    },
    'types': {
        'application/json': {
            'name': 'application/json',
            'params': {},
            'suffixes': ['.json'],
        },
        'application/xml': {
            'name': 'application/xml',
            'params': {},
            'suffixes': ['.xml'],
        },
        'application/vnd.fooapp': {
            'name': 'application/vnd.fooapp',
            'params': {},
        },
    },
}It is also worth noting that the type “application/vnd.fooapp” has no
configured suffixes, and so thesuffixeskey is omitted from its
description.  Similarly, if a version was declared for which there was
no corresponding URI prefix, that version would not have aprefixeskey.Variables Associated with the “Content-Type” HeaderThere are three variables associated with the “Content-Type” header.
They are only set if a “Content-Type” header is set on the request,
and is matched by a type rule, and are described below.aversion.request_typeThis is the final content type for the body of the request, after
transformation by the type rule.  This value will also be used to
overwrite the “Content-Type” header.aversion.orig_request_typeThis is the name of the matching type rule.aversion.content_typeThis will be the original value of the “Content-Type” header.Variables Associated with the “Accept” HeaderThere are three variables associated with the “Accept” header.  They
are set if the requested content type can be determined.  The
requested content type may be determined from a URI suffix or from the
contents of the “Accept” header, and are described below.aversion.response_typeThis is the final content type requested by the client, after
transformation by the type rule.  This value will also be used to
overwrite the “Accept” header.aversion.orig_response_typeThis is the name of the matching type rule.  If the content type
was determined from a URI suffix, this value will beNone.aversion.acceptThis will be the original value of the “Accept” header.  If none
was present in the request (e.g., if the requested content type
was determined from a URI suffix rule), this value will beNone.Advanced AVersion ConfigurationThe discussion about theaversion.configWSGI environment variable
referred to parameters on versions, aliases, and content types.  These
parameters are specifically for the benefit of applications, and are
ignored by AVersion; they can be used for communicating important
information about the configured versions, aliases, and content types
to the applications, particularly the default application.To configure parameters on versions, simply add ‘key=”value”’ after
the version application name, e.g.:version.v1 = api_v1 key1="value1" key2="value2"For aliases, the syntax is similar:alias.v1.1 = v2 key1="value1" key2="value2"The syntax is a little more complex for content type rules; the
‘key=”value”’ tokens must be prefixed with “param:”, e.g.:type.application/json = version:"v%(version)s"
    param:key1="value1" param:key2="value2"Note that all values must be quoted.  Both double quotes and single
quotes are acceptable quote characters, and it is safe to include
spaces within the quoted text.There is one more advanced configuration topic.  By default, AVersion
overwrites the “Accept” and “Content-Type” headers.  Since the
information it would use for this overwriting is available in the WSGI
environment, it is possible to disable this behavior by setting theoverwrite_headersconfiguration key to “off”.  (Recognized values
are: “false”, “f”, “off”, “no”, “disable”, and “0”; “true”, “t”, “on”,
“yes”, “enable”, and any non-zero integer are recognized as “on”, the
default value foroverwrite_headers.)