Package: micropath
Themicropathweb application framework is a framework for
constructing web applications.  It is based on the UNIX philosophy: do
one thing and do it well.  Themicropathframework is ideally
suited for creating microservices, but can easily be used to construct
much larger applications as well.  Users extend themicropath.Controllerclass to implement their web application, and
instances of this class can then be used as WSGI applications.  Themicropathframework utilizes a tree structure for routing incoming
requests to the proper handler method, allowing for a significant
speedup over competing systems likeroutes, which matches an
incoming request against a list of regular expressions.  Themicropathframework also utilizes dependency injection techniques
to pass handler methods only the arguments they need to process the
request.  Finally, requests are represented using themicropath.Requestclass, which is a subclass ofwebob.Request;
handler methods can returnwebob.Responseobjects, or even plain
text (which will be wrapped in a “200 OK” response).TheControllerClassThe central functionality ofmicropathis theControllerclass; creating a WSGI web application usingmicropathis as
simple as creating a subclass ofmicropath.Controllerand
populating it.  Themicropath.Controller.__call__()method
implements a WSGI web application interface, meaning instances of theControllersubclass are full-fledged WSGI web applications.  There
is also amicropath.Controller.__init__()method, taking no
arguments, that all subclasses should ensure is called (either by not
overriding it, or by includingsuper(ClassName,self).__init__()in the implementation).  Aside from these two methods, all other
methods and attributes have names beginning withmicropath_(for
hook and utility methods and data values that developers may override
to customize functionality) or_micropath(for internal methods
and data values).  This document will touch on some of this
functionality, but for full details, see the help onmicropath.Controller.The most important part of any web application framework is
determining how to route the request to the handler that will act on
that request.  Themicropathframework relies on the special
functionsmicropath.path()andmicropath.bind()–both of which
return an internal typeElementthat includespath()andbind()methods as well–to construct the URL path that the web
application is expecting.  Under the covers, this creates a tree-like
object, rooted at the controller, for traversing the URL path of a
request, and should be considerably faster than other frameworks,
which often iterate over a list of regular expressions.  These path
elements are then bound to handler methods using the@micropath.route()decorator (for requests at the root of the
controller) or@Element.route()decorator (for requests relative
to anElementreturned by amicropath.path()ormicropath.bind()), which takes as arguments the HTTP methods that
should trigger the handler.  (Note: either form of the@route()decorator must be the outer-most decorator on a helper method–that
is, the@route()must be the very first decorator in the source
file.  This decorator stores the function that is passed to it in theElementtree, so any decorators that occur before it in the source
file will not be invoked when handling a request, even though they
would be if directly accessing the method through the class instance.)The following example should help clarify how these components work
together.  The example is a part of a REST-style web API for a library
(the dead-tree kind) for handling subscribers:class SubscriberController(micropath.Controller):
    @micropath.route('get')
    def index(self):
        # Return a list of subscribers
        ...

    @micropath.route('post')
    def create(self, json_body):
        # Create a new subscriber with details from the JSON body
        ...

    # Bind a subscriber ID to the next path element.  Since no
    # name is passed to ``bind()``, it will default to the
    # variable name "sub_id" (this is done by the metaclass).
    sub_id = micropath.bind()

    @sub_id.route('get')
    def get(self, sub_id):
        # Return the details about a specific subscriber
        ...

    @sub_id.route('put')
    def update(self, sub_id, json_body):
        # Update the subscriber with details from the JSON body
        ...

    @sub_id.route('delete')
    def delete(self, sub_id):
        # Delete the subscriber
        ...With the above example, an HTTP GET request to “/” would map to theindex()method, while an HTTP PUT request to “/1234” would callupdate()withsub_id="1234"andjson_bodybeing the result
of callingjson.loads()on the request body.  It’s also worth
pointing out thatmicropathimplements a default handler for the
HTTP OPTIONS method; if no handler method has the HTTP OPTIONS method
routed to it,micropathwill return a “204 No Content” response
with the “Allow” header set based on the routed methods.  For
instance, if we sent the HTTP OPTIONS request to “/1234”, the “Allow”
header would contain the string “DELETE,GET,HEAD,PUT,OPTIONS”.  (The
“HEAD” HTTP method is automatically converted to “GET” by themicropathrouting algorithm and so will be present in “Allow”
anytime “GET” is; and “OPTIONS” is always added, since there’s a
default implementation.)In our toy web API, we might also wish to know what books a given
subscriber has checked out.  There are multiple ways this could be
handled, but for the purposes of the example, we’ll assume that we
want a REST resource off the subscriber–e.g., “/1234/books” would
list the books the subscriber has checked out, “/1234/books/5678”
would get details on the checked-out book with id “5678”, etc.  Withmicropath, there are two ways of accomplishing this; the first way
would be to add the following lines to theSubscriberControllerclass:# Bind the "books" path element after the subscriber ID
books = sub_id.path()

@books.route('get')
def books_index(self, sub_id):
    # Return a list of checked-out books
    ...

@books.route('post')
def books_create(self, sub_id, json_body):
    # Create (check out) a book under the subscriber from the JSON
    # body
    ...

# Bind a book ID to the next path element
book_id = books.bind()

@book_id.route('get')
def book_get(self, sub_id, book_id):
    # Return the details about a specific book
    ...

@book_id.route('put')
def book_update(self, sub_id, book_id, json_body):
    # Update the book with details from the JSON body
    ...

@book_id.route('delete')
def book_delete(self, sub_id, book_id):
    # Delete (check in) the book from the subscriber
    ...With a simple API, or a microservice-style API, this scheme is
perfectly fine, but for large APIs, the size of the controller class
could become problematic very quickly.  Thus,micropathprovides
another way to accomplish this task: create aBookControllerclass
providing the functionality for the book resource, thenmountit on
theSubscriberControllerlike so:# The path() call is given the name "books" by the metaclass; the
# mount() method configures the path element to delegate requests
# to that path to the BookController class.  The BookController
# class will be instantiated when SubscriberController is,
# assuming that the __init__() method is not overridden, or that
# the superclass method is called.
books = sub_id.path().mount(BookController)Path Binding Validation and TranslationIn the examples above, the values assigned tosub_idandbook_idare passed as simple strings to the hook methods.
However, bindings can also have validators and formatters: avalidatoris a method that is passed thevalueargument (and any
other request elements that can be injected, including bindings from
earlier in the path).  The validator should validate that the value is
legal and return whatever object should be passed to handlers.  This
could be used to, for instance, resolve a subscriber ID into an actual
subscriber model object that would subsequently be passed to the
handler methods.  Validators should not raise just any exception,
however; they may raise any of the exceptions contained inwebob.exc, which will cause a suitable error to be returned to the
user, or they may raisemicropath.SkipBinding, which will
ultimately result in returning a 404 to the client.  Any other
exception will result in a 500 error being returned.In addition to the validator, a binding may have aformatter; this
is a function that will be passed the object that was passed tomicropath.Request.url_for()for that binding, and must return a
string suitable for inclusion in the URL.  An application that uses
theurl_for()method should either provide a formatter or ensure
that the object has an implemented__str__()method.Validators and formatters are set by decorating methods with the@validatorand@formatterdecorators, respectively.  For
instance, for theSubscriberControllerexample above, the
following would set the validator and formatter functions forsub_id:@sub_id.validator
def sub_id_validator(self, value):
    ...

@sub_id.formatter
def sub_id_formatter(self, value):
    ...RequestsHandler methods can request theRequestobject by listingrequestamong their arguments.  TheRequestclass used bymicropathis a subclass ofwebob.Request, which provides two
additional properties and an additional function.  Theinjectorproperty contains a dictionary-like class which is used formicropath’s dependency injection system, andbase_pathcontains the value ofscript_nameat the time the request was
constructed by the__call__()method ofController.  (The
routing algorithm ofControllermodifiesscript_nameandpath_infoas it routes the request, so a handler method always
seesscript_nameas the path to that handler method.)  Thebase_pathis thus the path to the rootControllerclass, and
is used by theurl_for()method.Theurl_for()method allows an application to construct an
absolute URL for any other handler method in the application.  The
first (and only) positional argument that should be passed toRequest.url_for()should be the handler method in question, and
keyword arguments specify the values for bindings.  Note that the
method reference must be to an instance method; passing something likeSubscriberController.indexis an error; use something likeself.index.  It should also be noted that handler methods can
request a reference to the root controller of the WSGI application by
listingroot_controlleramong their arguments.  Finally, mounted
controllers can be referenced using the mount point; in the example
above, where aBookControlleris mounted on aSubscriberController, theindex()method of theBookControllercould be referenced usingroot_controller.books.index.Configuration of aControllerInstanceThemicropathframework is not opinionated about the
implementation of the class__init__()method, other than
requiring, for thread safety purposes, that the superclass’s
constructor is called.  This means that applications can provide
configuration information at class construction time.  By default,
mounted classes are passed only keyword arguments provided to themount()method (which, typically, must be constants; this
mechanism is intended to allow a controller to tailor its behavior
depending on where it is mounted); however, mounted class construction
can be customized by overriding themicropath_construct()method
of the controller class onto which another controller is mounted.
This means that configuration information can be propagated to the
other controllers quite easily.Dependency Injection and Wrapping DecoratorsHandler methods inmicropathare invoked using dependency
injection, passing them the arguments that are declared as part of the
method signature.  However, handler methods are often additionally
decorated with wrapping-type decorators; that is, the decorator
creates a function, typically taking*argsand**kwargs, does
some processing, and then invokes the decorated function (or not,
depending on what the decorator is intended to do).  This affects the
function signature seen by the dependency injector, and could cause
spurious failures.To counter this problem, themicropathframework provides a
variation of@functools.wraps().  The@micropath.wraps()decorator functions similarly to the@functools.wraps()decorator,
but has some additional properties.  First, on Python 2.7, it ensures
the__wrapped__attribute is set (this is implemented by Python
3’s@functools.wraps(), but not present in Python 2.7’s version);
this makes it easier to get the underlying function, which could be
useful for unit testing.  Second, the@micropath.wraps()decorator
accepts three additional, optional keyword arguments:providescan
be a list of keyword arguments that the wrapped function may want that
are provided by the decorator;requiredis a list of keyword
arguments that are required by the decorator itself; andoptionalis a list of keyword arguments that may be provided if they’re
available in the injector.In addition to the@micropath.wraps()decorator, themicropathframework also provides themicropath.call_wrapped()utility
function.  This function takes as arguments the wrapped function, a
tuple of positional arguments, and a dictionary of keyword arguments,
and invokes the function using the injector machinery, returning the
value of calling the function.  Amicropathdecorator may also
wish to know if specific arguments are requested by the function; this
may be determined usingmicropath.wants(), which takes as
arguments the wrapped function and the name of a keyword argument, and
returns a boolean indicating whether the function wants the specified
keyword argument.Using the@micropath.wraps()decorator and themicropath.call_wrapped()function, function decorators can be
created that wrap a handler method without disrupting the dependency
injection mechanism that is integral to howmicropathcalls
handler methods.Customizing Request HandlingThemicropathframework provides a number of ways of customizing
request handling.  First of all, the class used for representing a
request can be set by overriding the value ofController.micropath_request; by default, this value ismicropath.Request.  (It is highly recommended that custom request
classes extendmicropath.Request, so that all functionality is
available.)  Second, the request attributes that are available for
dependency injection are stored in theController.micropath_request_attrsdictionary; additional
attributes can be added by copyingController.micropath_request_attrsand adding additional entries
to it.  The keys of this dictionary will be the argument names, and if
the value isNonethey will also name the attribute; otherwise,
the value should be the name of the request attribute.After the request is constructed, themicropathframework invokes
theController.micropath_prepare_injector()hook method.  The
default implementation does nothing, but this method can be overridden
in the root controller of an application to implement any desired
behavior: authentication headers can be verified, additional data can
be added to the dependency injector, etc.  This is the last step
before traversing the element tree and invoking the proper handler
method.Several other hook methods exist in themicropath.Controllerclass.  For instance, if an error occurs while attempting to evaluate
a request attribute for injection to a handler method, theController.micropath_request_error()hook method is invoked; its
default implementation will return a 400 error to the client.  If, on
the other hand, an exception occurs in a handler method, theController.micropath_server_error()hook method is invoked, which
will, by default, return a 500 error to the client.  If the client’s
URL could not be mapped to a controller, theController.micropath_not_found()hook method is called to generate
a 404 error, andController.micropath_not_implemented()is called
if the URL exists, but the specified HTTP method is not routed to a
handler.  Finally, theController.micropath_options()provides the
default implementation for the HTTP OPTIONS method; by default, it
returns a 204 response with the “Allow” header containing a
comma-separated list of recognized HTTP methods.Methods Requestingpath_infoBy default, the entire URL must be consumed for a handler method to be
invoked.  However, a handler method may request thepath_infoattribute of the request; if the handler method represents the longest
match for the requested URL, the handler will be invoked with the
remaining components of the URL path passed as thepath_infoparameter of the handler method.  This effectively inhibits the usual
behavior of returning a 404 response.Launching amicropathApplicationInstances ofmicropath.Controllersubclasses are fully fledged
WSGI applications.  Many WSGI servers want a module with anapplicationcallable present that is the actual WSGI application;
this attribute may simply be an instance of the root controller.  The
exact semantics depend on the WSGI server, so refer to the
documentation of the server for more details about how to provide the
WSGI application to it.Instances ofmicropath.Controlleralso have amicropath_run()utility method.  This method simply uses the Python standard library’s
built-inwsgirefpackage to launch a simple web server.  By
default, this server will run on the loopback interface (“localhost”,
or, more technically, “127.0.0.1”) on port 8000, although that can be
controlled using arguments to the method.  Note that this isNOT
RECOMMENDEDfor production systems; this simple server does not
attempt to handle threading, exceptional error handling, SSL, or a
host of other issues that real, production-ready HTTP and WSGI servers
handle.  This is simply meant to simplify testing an application on a
developer’s local laptop.The HEAD HTTP MethodThe HTTP specification specifically states that the HEAD method should
act identically to the GET method, except that no body is sent in the
response.  Given that, themicropathframework treats HEAD as if
it were GET.  In particular, a method that has HEAD routed to it will
never be called unless GET is also routed to that method.
Nevertheless, themethodattribute of the request will contain the
actual HTTP method that was sent.  This also means that the default
OPTIONS response will include HEAD if GET is routed, without any
additional effort on the part of the user.Controller InheritanceThe element tree built for any givenmicropath.Controllersubclass
is unique to that specific class.  In particular, this means that a
class that inherits from another class doesnotinherit the URL or
method routing from that class, nor does it inherit the mount points.
However, the hook methods and other customizing data elements are
inherited, as are the actual handler methods and any other methods and
data elements.  Users may take advantage of this by constructing a
base controller class with the needed features, then basing the
application controller classes on that base controller class.