{ "info": { "author": "Kevin L. Mitchell", "author_email": "klmitch@mit.edu", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Environment :: No Input/Output (Daemon)", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP :: WSGI" ], "description": "=======================================\nThe micropath Web Application Framework\n=======================================\n\n.. image:: https://travis-ci.org/klmitch/micropath.svg?branch=master\n :target: https://travis-ci.org/klmitch/micropath\n\nThe ``micropath`` web application framework is a framework for\nconstructing web applications. It is based on the UNIX philosophy: do\none thing and do it well. The ``micropath`` framework is ideally\nsuited for creating microservices, but can easily be used to construct\nmuch larger applications as well. Users extend the\n``micropath.Controller`` class to implement their web application, and\ninstances of this class can then be used as WSGI applications. The\n``micropath`` framework utilizes a tree structure for routing incoming\nrequests to the proper handler method, allowing for a significant\nspeedup over competing systems like ``routes``, which matches an\nincoming request against a list of regular expressions. The\n``micropath`` framework also utilizes dependency injection techniques\nto pass handler methods only the arguments they need to process the\nrequest. Finally, requests are represented using the\n``micropath.Request`` class, which is a subclass of ``webob.Request``;\nhandler methods can return ``webob.Response`` objects, or even plain\ntext (which will be wrapped in a \"200 OK\" response).\n\nThe ``Controller`` Class\n========================\n\nThe central functionality of ``micropath`` is the ``Controller``\nclass; creating a WSGI web application using ``micropath`` is as\nsimple as creating a subclass of ``micropath.Controller`` and\npopulating it. The ``micropath.Controller.__call__()`` method\nimplements a WSGI web application interface, meaning instances of the\n``Controller`` subclass are full-fledged WSGI web applications. There\nis also a ``micropath.Controller.__init__()`` method, taking no\narguments, that all subclasses should ensure is called (either by not\noverriding it, or by including ``super(ClassName, self).__init__()``\nin the implementation). Aside from these two methods, all other\nmethods and attributes have names beginning with ``micropath_`` (for\nhook and utility methods and data values that developers may override\nto customize functionality) or ``_micropath`` (for internal methods\nand data values). This document will touch on some of this\nfunctionality, but for full details, see the help on\n``micropath.Controller``.\n\nThe most important part of any web application framework is\ndetermining how to route the request to the handler that will act on\nthat request. The ``micropath`` framework relies on the special\nfunctions ``micropath.path()`` and ``micropath.bind()``--both of which\nreturn an internal type ``Element`` that includes ``path()`` and\n``bind()`` methods as well--to construct the URL path that the web\napplication is expecting. Under the covers, this creates a tree-like\nobject, rooted at the controller, for traversing the URL path of a\nrequest, and should be considerably faster than other frameworks,\nwhich often iterate over a list of regular expressions. These path\nelements are then bound to handler methods using the\n``@micropath.route()`` decorator (for requests at the root of the\ncontroller) or ``@Element.route()`` decorator (for requests relative\nto an ``Element`` returned by a ``micropath.path()`` or\n``micropath.bind()``), which takes as arguments the HTTP methods that\nshould trigger the handler. (Note: either form of the ``@route()``\ndecorator must be the outer-most decorator on a helper method--that\nis, the ``@route()`` must be the very first decorator in the source\nfile. This decorator stores the function that is passed to it in the\n``Element`` tree, so any decorators that occur before it in the source\nfile will not be invoked when handling a request, even though they\nwould be if directly accessing the method through the class instance.)\n\nThe following example should help clarify how these components work\ntogether. The example is a part of a REST-style web API for a library\n(the dead-tree kind) for handling subscribers::\n\n class SubscriberController(micropath.Controller):\n @micropath.route('get')\n def index(self):\n # Return a list of subscribers\n ...\n\n @micropath.route('post')\n def create(self, json_body):\n # Create a new subscriber with details from the JSON body\n ...\n\n # Bind a subscriber ID to the next path element. Since no\n # name is passed to ``bind()``, it will default to the\n # variable name \"sub_id\" (this is done by the metaclass).\n sub_id = micropath.bind()\n\n @sub_id.route('get')\n def get(self, sub_id):\n # Return the details about a specific subscriber\n ...\n\n @sub_id.route('put')\n def update(self, sub_id, json_body):\n # Update the subscriber with details from the JSON body\n ...\n\n @sub_id.route('delete')\n def delete(self, sub_id):\n # Delete the subscriber\n ...\n\nWith the above example, an HTTP GET request to \"/\" would map to the\n``index()`` method, while an HTTP PUT request to \"/1234\" would call\n``update()`` with ``sub_id=\"1234\"`` and ``json_body`` being the result\nof calling ``json.loads()`` on the request body. It's also worth\npointing out that ``micropath`` implements a default handler for the\nHTTP OPTIONS method; if no handler method has the HTTP OPTIONS method\nrouted to it, ``micropath`` will return a \"204 No Content\" response\nwith the \"Allow\" header set based on the routed methods. For\ninstance, if we sent the HTTP OPTIONS request to \"/1234\", the \"Allow\"\nheader would contain the string \"DELETE,GET,HEAD,PUT,OPTIONS\". (The\n\"HEAD\" HTTP method is automatically converted to \"GET\" by the\n``micropath`` routing algorithm and so will be present in \"Allow\"\nanytime \"GET\" is; and \"OPTIONS\" is always added, since there's a\ndefault implementation.)\n\nIn our toy web API, we might also wish to know what books a given\nsubscriber has checked out. There are multiple ways this could be\nhandled, but for the purposes of the example, we'll assume that we\nwant a REST resource off the subscriber--e.g., \"/1234/books\" would\nlist the books the subscriber has checked out, \"/1234/books/5678\"\nwould get details on the checked-out book with id \"5678\", etc. With\n``micropath``, there are two ways of accomplishing this; the first way\nwould be to add the following lines to the ``SubscriberController``\nclass::\n\n # Bind the \"books\" path element after the subscriber ID\n books = sub_id.path()\n\n @books.route('get')\n def books_index(self, sub_id):\n # Return a list of checked-out books\n ...\n\n @books.route('post')\n def books_create(self, sub_id, json_body):\n # Create (check out) a book under the subscriber from the JSON\n # body\n ...\n\n # Bind a book ID to the next path element\n book_id = books.bind()\n\n @book_id.route('get')\n def book_get(self, sub_id, book_id):\n # Return the details about a specific book\n ...\n\n @book_id.route('put')\n def book_update(self, sub_id, book_id, json_body):\n # Update the book with details from the JSON body\n ...\n\n @book_id.route('delete')\n def book_delete(self, sub_id, book_id):\n # Delete (check in) the book from the subscriber\n ...\n\nWith a simple API, or a microservice-style API, this scheme is\nperfectly fine, but for large APIs, the size of the controller class\ncould become problematic very quickly. Thus, ``micropath`` provides\nanother way to accomplish this task: create a ``BookController`` class\nproviding the functionality for the book resource, then *mount* it on\nthe ``SubscriberController`` like so::\n\n # The path() call is given the name \"books\" by the metaclass; the\n # mount() method configures the path element to delegate requests\n # to that path to the BookController class. The BookController\n # class will be instantiated when SubscriberController is,\n # assuming that the __init__() method is not overridden, or that\n # the superclass method is called.\n books = sub_id.path().mount(BookController)\n\nPath Binding Validation and Translation\n=======================================\n\nIn the examples above, the values assigned to ``sub_id`` and\n``book_id`` are passed as simple strings to the hook methods.\nHowever, bindings can also have validators and formatters: a\n*validator* is a method that is passed the ``value`` argument (and any\nother request elements that can be injected, including bindings from\nearlier in the path). The validator should validate that the value is\nlegal and return whatever object should be passed to handlers. This\ncould be used to, for instance, resolve a subscriber ID into an actual\nsubscriber model object that would subsequently be passed to the\nhandler methods. Validators should not raise just any exception,\nhowever; they may raise any of the exceptions contained in\n``webob.exc``, which will cause a suitable error to be returned to the\nuser, or they may raise ``micropath.SkipBinding``, which will\nultimately result in returning a 404 to the client. Any other\nexception will result in a 500 error being returned.\n\nIn addition to the validator, a binding may have a *formatter*; this\nis a function that will be passed the object that was passed to\n``micropath.Request.url_for()`` for that binding, and must return a\nstring suitable for inclusion in the URL. An application that uses\nthe ``url_for()`` method should either provide a formatter or ensure\nthat the object has an implemented ``__str__()`` method.\n\nValidators and formatters are set by decorating methods with the\n``@validator`` and ``@formatter`` decorators, respectively. For\ninstance, for the ``SubscriberController`` example above, the\nfollowing would set the validator and formatter functions for\n``sub_id``::\n\n @sub_id.validator\n def sub_id_validator(self, value):\n ...\n\n @sub_id.formatter\n def sub_id_formatter(self, value):\n ...\n\nRequests\n========\n\nHandler methods can request the ``Request`` object by listing\n``request`` among their arguments. The ``Request`` class used by\n``micropath`` is a subclass of ``webob.Request``, which provides two\nadditional properties and an additional function. The ``injector``\nproperty contains a dictionary-like class which is used for\n``micropath``'s dependency injection system, and ``base_path``\ncontains the value of ``script_name`` at the time the request was\nconstructed by the ``__call__()`` method of ``Controller``. (The\nrouting algorithm of ``Controller`` modifies ``script_name`` and\n``path_info`` as it routes the request, so a handler method always\nsees ``script_name`` as the path to that handler method.) The\n``base_path`` is thus the path to the root ``Controller`` class, and\nis used by the ``url_for()`` method.\n\nThe ``url_for()`` method allows an application to construct an\nabsolute URL for any other handler method in the application. The\nfirst (and only) positional argument that should be passed to\n``Request.url_for()`` should be the handler method in question, and\nkeyword arguments specify the values for bindings. Note that the\nmethod reference must be to an instance method; passing something like\n``SubscriberController.index`` is an error; use something like\n``self.index``. It should also be noted that handler methods can\nrequest a reference to the root controller of the WSGI application by\nlisting ``root_controller`` among their arguments. Finally, mounted\ncontrollers can be referenced using the mount point; in the example\nabove, where a ``BookController`` is mounted on a\n``SubscriberController``, the ``index()`` method of the\n``BookController`` could be referenced using\n``root_controller.books.index``.\n\nConfiguration of a ``Controller`` Instance\n==========================================\n\nThe ``micropath`` framework is not opinionated about the\nimplementation of the class ``__init__()`` method, other than\nrequiring, for thread safety purposes, that the superclass's\nconstructor is called. This means that applications can provide\nconfiguration information at class construction time. By default,\nmounted classes are passed only keyword arguments provided to the\n``mount()`` method (which, typically, must be constants; this\nmechanism is intended to allow a controller to tailor its behavior\ndepending on where it is mounted); however, mounted class construction\ncan be customized by overriding the ``micropath_construct()`` method\nof the controller class onto which another controller is mounted.\nThis means that configuration information can be propagated to the\nother controllers quite easily.\n\nDependency Injection and Wrapping Decorators\n============================================\n\nHandler methods in ``micropath`` are invoked using dependency\ninjection, passing them the arguments that are declared as part of the\nmethod signature. However, handler methods are often additionally\ndecorated with wrapping-type decorators; that is, the decorator\ncreates a function, typically taking ``*args`` and ``**kwargs``, does\nsome processing, and then invokes the decorated function (or not,\ndepending on what the decorator is intended to do). This affects the\nfunction signature seen by the dependency injector, and could cause\nspurious failures.\n\nTo counter this problem, the ``micropath`` framework provides a\nvariation of ``@functools.wraps()``. The ``@micropath.wraps()``\ndecorator functions similarly to the ``@functools.wraps()`` decorator,\nbut has some additional properties. First, on Python 2.7, it ensures\nthe ``__wrapped__`` attribute is set (this is implemented by Python\n3's ``@functools.wraps()``, but not present in Python 2.7's version);\nthis makes it easier to get the underlying function, which could be\nuseful for unit testing. Second, the ``@micropath.wraps()`` decorator\naccepts three additional, optional keyword arguments: ``provides`` can\nbe a list of keyword arguments that the wrapped function may want that\nare provided by the decorator; ``required`` is a list of keyword\narguments that are required by the decorator itself; and ``optional``\nis a list of keyword arguments that may be provided if they're\navailable in the injector.\n\nIn addition to the ``@micropath.wraps()`` decorator, the ``micropath``\nframework also provides the ``micropath.call_wrapped()`` utility\nfunction. This function takes as arguments the wrapped function, a\ntuple of positional arguments, and a dictionary of keyword arguments,\nand invokes the function using the injector machinery, returning the\nvalue of calling the function. A ``micropath`` decorator may also\nwish to know if specific arguments are requested by the function; this\nmay be determined using ``micropath.wants()``, which takes as\narguments the wrapped function and the name of a keyword argument, and\nreturns a boolean indicating whether the function wants the specified\nkeyword argument.\n\nUsing the ``@micropath.wraps()`` decorator and the\n``micropath.call_wrapped()`` function, function decorators can be\ncreated that wrap a handler method without disrupting the dependency\ninjection mechanism that is integral to how ``micropath`` calls\nhandler methods.\n\nCustomizing Request Handling\n============================\n\nThe ``micropath`` framework provides a number of ways of customizing\nrequest handling. First of all, the class used for representing a\nrequest can be set by overriding the value of\n``Controller.micropath_request``; by default, this value is\n``micropath.Request``. (It is highly recommended that custom request\nclasses extend ``micropath.Request``, so that all functionality is\navailable.) Second, the request attributes that are available for\ndependency injection are stored in the\n``Controller.micropath_request_attrs`` dictionary; additional\nattributes can be added by copying\n``Controller.micropath_request_attrs`` and adding additional entries\nto it. The keys of this dictionary will be the argument names, and if\nthe value is ``None`` they will also name the attribute; otherwise,\nthe value should be the name of the request attribute.\n\nAfter the request is constructed, the ``micropath`` framework invokes\nthe ``Controller.micropath_prepare_injector()`` hook method. The\ndefault implementation does nothing, but this method can be overridden\nin the root controller of an application to implement any desired\nbehavior: authentication headers can be verified, additional data can\nbe added to the dependency injector, etc. This is the last step\nbefore traversing the element tree and invoking the proper handler\nmethod.\n\nSeveral other hook methods exist in the ``micropath.Controller``\nclass. For instance, if an error occurs while attempting to evaluate\na request attribute for injection to a handler method, the\n``Controller.micropath_request_error()`` hook method is invoked; its\ndefault implementation will return a 400 error to the client. If, on\nthe other hand, an exception occurs in a handler method, the\n``Controller.micropath_server_error()`` hook method is invoked, which\nwill, by default, return a 500 error to the client. If the client's\nURL could not be mapped to a controller, the\n``Controller.micropath_not_found()`` hook method is called to generate\na 404 error, and ``Controller.micropath_not_implemented()`` is called\nif the URL exists, but the specified HTTP method is not routed to a\nhandler. Finally, the ``Controller.micropath_options()`` provides the\ndefault implementation for the HTTP OPTIONS method; by default, it\nreturns a 204 response with the \"Allow\" header containing a\ncomma-separated list of recognized HTTP methods.\n\nMethods Requesting ``path_info``\n================================\n\nBy default, the entire URL must be consumed for a handler method to be\ninvoked. However, a handler method may request the ``path_info``\nattribute of the request; if the handler method represents the longest\nmatch for the requested URL, the handler will be invoked with the\nremaining components of the URL path passed as the ``path_info``\nparameter of the handler method. This effectively inhibits the usual\nbehavior of returning a 404 response.\n\nLaunching a ``micropath`` Application\n=====================================\n\nInstances of ``micropath.Controller`` subclasses are fully fledged\nWSGI applications. Many WSGI servers want a module with an\n``application`` callable present that is the actual WSGI application;\nthis attribute may simply be an instance of the root controller. The\nexact semantics depend on the WSGI server, so refer to the\ndocumentation of the server for more details about how to provide the\nWSGI application to it.\n\nInstances of ``micropath.Controller`` also have a ``micropath_run()``\nutility method. This method simply uses the Python standard library's\nbuilt-in ``wsgiref`` package to launch a simple web server. By\ndefault, this server will run on the loopback interface (\"localhost\",\nor, more technically, \"127.0.0.1\") on port 8000, although that can be\ncontrolled using arguments to the method. Note that this is *NOT\nRECOMMENDED* for production systems; this simple server does not\nattempt to handle threading, exceptional error handling, SSL, or a\nhost of other issues that real, production-ready HTTP and WSGI servers\nhandle. This is simply meant to simplify testing an application on a\ndeveloper's local laptop.\n\nThe HEAD HTTP Method\n====================\n\nThe HTTP specification specifically states that the HEAD method should\nact identically to the GET method, except that no body is sent in the\nresponse. Given that, the ``micropath`` framework treats HEAD as if\nit were GET. In particular, a method that has HEAD routed to it will\nnever be called unless GET is also routed to that method.\nNevertheless, the ``method`` attribute of the request will contain the\nactual HTTP method that was sent. This also means that the default\nOPTIONS response will include HEAD if GET is routed, without any\nadditional effort on the part of the user.\n\nController Inheritance\n======================\n\nThe element tree built for any given ``micropath.Controller`` subclass\nis unique to that specific class. In particular, this means that a\nclass that inherits from another class does *not* inherit the URL or\nmethod routing from that class, nor does it inherit the mount points.\nHowever, the hook methods and other customizing data elements are\ninherited, as are the actual handler methods and any other methods and\ndata elements. Users may take advantage of this by constructing a\nbase controller class with the needed features, then basing the\napplication controller classes on that base controller class.\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/klmitch/micropath", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "micropath", "package_url": "https://pypi.org/project/micropath/", "platform": "", "project_url": "https://pypi.org/project/micropath/", "project_urls": { "Homepage": "https://github.com/klmitch/micropath" }, "release_url": "https://pypi.org/project/micropath/0.1.0/", "requires_dist": null, "requires_python": "", "summary": "The micropath Web Application Framework", "version": "0.1.0" }, "last_serial": 3924549, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "389f8ff2d1915f71adbb79123b32f1d3", "sha256": "8af4b8e3b8535e419bd5d27020d3e1c9e36f89d7efbad14216e6f86a91cccca8" }, "downloads": -1, "filename": "micropath-0.1.0.tar.gz", "has_sig": false, "md5_digest": "389f8ff2d1915f71adbb79123b32f1d3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46917, "upload_time": "2018-06-03T01:05:40", "url": "https://files.pythonhosted.org/packages/64/89/020cba00f9dbc4c91eba17e5e4a4fdd1afc8c5de208ecc9e558daba0c489/micropath-0.1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "389f8ff2d1915f71adbb79123b32f1d3", "sha256": "8af4b8e3b8535e419bd5d27020d3e1c9e36f89d7efbad14216e6f86a91cccca8" }, "downloads": -1, "filename": "micropath-0.1.0.tar.gz", "has_sig": false, "md5_digest": "389f8ff2d1915f71adbb79123b32f1d3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 46917, "upload_time": "2018-06-03T01:05:40", "url": "https://files.pythonhosted.org/packages/64/89/020cba00f9dbc4c91eba17e5e4a4fdd1afc8c5de208ecc9e558daba0c489/micropath-0.1.0.tar.gz" } ] }