{"id":16663827,"url":"https://github.com/jonathanj/fugue","last_synced_at":"2025-12-29T02:03:59.934Z","repository":{"id":149213978,"uuid":"136291661","full_name":"jonathanj/fugue","owner":"jonathanj","description":"Fugue: HTTP in two voices","archived":false,"fork":false,"pushed_at":"2018-06-19T07:43:31.000Z","size":87,"stargazers_count":0,"open_issues_count":6,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-06T08:53:19.401Z","etag":null,"topics":["http","python","python27","twisted","twisted-library","web-service"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonathanj.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":"CONTRIBUTING.rst","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.rst","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-06T07:41:23.000Z","updated_at":"2018-06-19T07:43:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"18460730-4af1-48c6-8d1e-adcda509f6b1","html_url":"https://github.com/jonathanj/fugue","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanj%2Ffugue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanj%2Ffugue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanj%2Ffugue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanj%2Ffugue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonathanj","download_url":"https://codeload.github.com/jonathanj/fugue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243307334,"owners_count":20270256,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","python","python27","twisted","twisted-library","web-service"],"created_at":"2024-10-12T10:42:11.802Z","updated_at":"2025-12-29T02:03:59.878Z","avatar_url":"https://github.com/jonathanj.png","language":"Python","readme":"=========================\nFugue: HTTP in two voices\n=========================\n\n.. image:: https://travis-ci.org/jonathanj/fugue.svg?branch=master\n   :target: https://travis-ci.org/jonathanj/fugue\n   :alt: CI status\n\n.. image:: https://codecov.io/github/jonathanj/fugue/coverage.svg?branch=master\n   :target: https://codecov.io/github/jonathanj/fugue?branch=master\n   :alt: Coverage\n\n.. teaser-begin\n\nFugue is a Python implementation of the *interceptor* concept as seen in, and\nheavily inspired by, `Pedestal`_. It is currently built on `Twisted`_, the\nevent-driven networking engine for Python, and `Pyrsistent`_ for immutable data\nstructures.\n\nBriefly, an interceptor is a reusable, composable component responsible for an\nindividual aspect of the overall behaviour of a web service, such as parsing a\nquery string or performing content negotiation. Combining interceptors produces\nan execution chain that is easily expressed, understood and tested; logic is\nkept small and isolated.\n\n.. XXX: Link to docs, when they exist.\n\n.. _Pedestal: http://pedestal.io/\n.. _Twisted: https://twistedmatrix.com/\n.. _Pyrsistent: https://github.com/tobgu/pyrsistent\n\n\n----------\nMotivation\n----------\n\nThe motivation for Fugue was heavily inspired by `Pedestal`_ and the idea of a\nmore composable, functional, reusable way of describing a web application that\ncan (hopefully!) remain simple to reason about and test even as application\ncomplexity grows.\n\nThe goal for Fugue is to give web application developers the freedom to focus on\nthe logic of their application and the ability to easily build up an applicaton\nout of small reusable functions, without having to concern themselves with the\ndetails of their web server.\n\nTwisted Web is a production-ready HTTP (and HTTP2!) server implemented in pure\nPython using `Twisted`_. It is mature, well supported and can be embedded (and\ncustomized!) in your Python application. `Nevow`_ is a web framework built on\nTwisted and Twisted Web, offering an HTTP server-push \"widget\" system, templates\nand other features.\n\nNevow's resource model is very closely based on Twisted Web's but is\nunfortunately incompatible, Twisted Web's resource model was implemented nearly\ntwo decades ago and hasn't seen much change since then. Using it can be quite\ncumbersome for complex web applications, and understanding such a system tends\nto be even more difficult. Various attempts exist—`Klein`_, and even my\n`own`_—to improve the developer experience of using Twisted Web, often\nattempting to smooth over or hide as much of the resource model as possible.\n\nIdeally a web application developer would only concern themselves with\nprocessing some input data, applying some application / business logic to that\ndata (possibly over several incremental steps) and producing an output. At the\nfringes of the application are the uninteresting, mechanical details: The\nresource model; writing a request back to the network; unserializing requests\nand serializing responses; and so forth.\n\nMaybe Fugue can be that ideal.\n\n.. _Nevow: https://github.com/twisted/nevow\n.. _Klein: https://github.com/twisted/klein\n.. _own: https://github.com/jonathanj/txspinneret\n\n\n------------\nInterceptors\n------------\n\nInterceptors are the foundation of Fugue, and most of the library is dedicated\nto providing interceptors that are useful for building HTTP services.\n\nAn interceptor is a pair of unary functions that accept a `context map`_—an\nimmutable data structure—and must eventually return a context map. One function\n(``enter``) is called on the way \"in\" and another (``leave``) is called on the\nway \"out\". Either function may be omitted and the effect is that the context map\nremains unchanged.\n\nInterceptors are combined to produce a particular order of execution, the\n\"enter\" stage is called in order for each interceptor with the—possibly\nmodified—context map flowing from one to the next. Once all interceptors have\nbeen called, the \"leave\" stage is called in reverse order for each interceptor\nthreading the context map—resulting from the \"enter\" stage—through them;\nillustrated below:\n\n::\n   \n     ┌───────────┐         ┌───────────┐\n     │Context map│         │Context map│\n     └─────┬─────┘         └─────▲─────┘\n           │                     │\n   ┌───────┼─────────────────────┼───────┐\n   │    ┌──▼──┐               ┌──┴──┐    │\n   │    │Enter│               │Leave│    │   Interceptor\n   │    └──┬──┘               └──▲──┘    │\n   └───────┼─────────────────────┼───────┘\n           │                     │\n   ┌───────┼─────────────────────┼───────┐\n   │    ┌──▼──┐               ┌──┴──┐    │\n   │    │Enter│               │Leave│    │   Interceptor\n   │    └──┬──┘               └──▲──┘    │\n   └───────┼─────────────────────┼───────┘\n           │                     │\n     ┌─────▼─────┐         ┌─────┴─────┐\n     │Context map├ ─ ─ ─ ─ ▶Context map│\n     └───────────┘         └───────────┘\n\nAsynchronous results, in the form of a Twisted `Deferred`_, may be returned from\nany stage of an interceptor; the effect is that execution of the interceptor\nchain is paused until the result becomes available.\n\nFugue keeps a queue of interceptors that have yet to be called in the context\nmap itself. Since interceptors are free to modify the context map, this means\nthey are also able to modify the remaining flow of execution! Terminating the\n\"enter\" stage is a matter of clearing the queue, extending it is a matter of\nenqueuing new interceptors; achieved by ``terminate`` and ``enqueue``\nrespectively.\n\n.. _Deferred: https://twistedmatrix.com/documents/current/core/howto/defer.html\n\nExample\n^^^^^^^\n\nA basic interceptor to attach a UUID to some ``uuid`` key on enter:\n\n.. code-block:: python\n\n   Interceptor(\n       name='uuid',\n       enter=lambda context: context.set(ns('uuid'), uuid4()))\n\nInterceptors executing after this example would find a ``ns('uuid')`` key in the\ncontext map containing a random UUID. In this case ``ns`` is some function\nintended to produce namespaced keys to avoid collisions with either internal or\nexternal keys. Fugue provides a basic function to help achieve this in the form\nof ``namespace``.\n\nA common pattern is to produce an interceptor from a function and capture the\narguments of the function (via a closure) within the interceptor's enter or\nleave functions. For example attaching a database connection to each request:\n\n.. code-block:: python\n\n   def attach_database(uri):\n       return Interceptor(\n           name='db',\n           enter=lambda context: context.set(ns('db'), connect_db(uri)))\n\n\n--------------\nError handling\n--------------\n\nErrors are a natural part of programming, however the normal methods of handling\nthem are not as useful within the context of an interceptor chain, if only\nbecause they may arise asynchronously.\n\nInstead Fugue traps synchronous and asynchronous errors within interceptors and\nattaches them to an ``ERROR`` key in the context map. The \"enter\" stage is\nterminated and the \"leave\" stage immediately begins, however as long as there is\nan ``ERROR`` key in the context map only the ``error`` function of interceptors\nalong the chain will be invoked.\n\nAn error may be handled by returning a context map without the ``ERROR`` key.\nWhen this happens the ``leave`` function of the next interceptor is invoked and\nthe \"leave\" stage continues as normal from that point.\n\nIf execution ends without the error having been handled it will be be raised\n(asynchronously, via a ``Deferred`` `errback`_.\n\n.. _errback: https://twistedmatrix.com/documents/current/core/howto/defer.html#errbacks\n\nError functions\n^^^^^^^^^^^^^^^\n\nAn interceptor's ``error`` function is invoked with the context map (devoid of\nan ``ERROR`` key, for convenience) and the value of the ``ERROR`` key.\n\nThe error function can do one of several things:\n\n1. Return the context map as-is. This is catching the error because there is no\n   longer an ``ERROR`` key present and execution will resume normally.\n2. Return the context map with the error reattached to the ``ERROR`` key. This\n   is reraising the error and the search for an error handler will continue.\n3. Raise a new error. This is the error handler encountering a new error trying\n   to handle the original error, the search for an error handler will continue\n   but for the new error instead.\n\n\n-----------\nContext map\n-----------\n\nA context map is passed to each interceptor's ``enter`` and ``leave`` functions.\nBelow are the basic keys you can expect to find, any key not listed below should\nbe considered an implementation detail subject to change, either in Fugue itself\nor the interceptor responsible for creating the key.\n\nIt should be noted that context map returned from each interceptor should be a\ntransformed version of the one received and *not* a new map. Interceptors may\narbitrarily add new keys that should be preserved.\n\n================ =============\n Key              Description\n================ =============\n``ERROR``        An object indicating a `Failure`_, in a ``failure`` attribute.\n``EXECUTION_ID`` A unique identifier set when the chain is executed.\n``QUEUE``        The interceptors left to execute, should be manipulated by\n                 ``enqueue``, ``terminate`` and ``terminate_when``.\n``TERMINATORS``  Predicates executed after each ``enter`` function, the\n                 \"enter\" stage is terminated if any return a true value.\n================ =============\n\n\nHTTP context map\n^^^^^^^^^^^^^^^^\n\nWhen using Fugue's HTTP request handling the ``REQUEST`` and ``RESPONSE`` keys\nwill be present, containing information about the request to process and the\nresponse to return.\n\nThe request map is attached before the first interceptor is executed, it\ndescribes the incoming HTTP request:\n\n====================== =============\n Key                    Description\n====================== =============\n``body``               ``file``\\-like object containing the body of the request.\n``content_type``       ``Content-Type`` header.\n``content_length``     ``Content-Length`` header.\n``character_encoding`` Content encoding of the ``Content-Type`` header, defaults\n                       to ``utf-8``.\n``headers``            Map of header names to vectors of header values.\n``request_method``     HTTP method.\n``uri``                `URL`_ the request is being made to.\n====================== =============\n\nThe response map is attached by any interceptor in the chain wishing to\ninfluence the HTTP response. If no response map exists when execution completes\nan HTTP 404 response is generated.\n\n=========== =============\n Key         Description\n=========== =============\n``status``  HTTP status code as an ``int``.\n``headers`` Optional map of HTTP response headers to include.\n``body``    Response body as ``bytes``.\n=========== =============\n\n.. XXX: Add keys omitted for brevity.\n\n.. _Failure: https://twistedmatrix.com/documents/current/api/twisted.python.failure.Failure.html\n.. _URL: http://hyperlink.readthedocs.io/en/latest/api.html#hyperlink.URL\n\n--------\nAdapters\n--------\n\nAdapters are the mechanism that bind the external world (such as a web server)\nto the internal world of interceptors. If interceptors consume and produce\nimmutable data via the context map then adapters transform some external\ninformation (such as an HTTP request) to and from that pure data.\n\nThis way the majority of the request processing (including application logic) is\nunconcerned with the particular web server implementation, the adapter enqueues\nthe necessary interceptor to transform incoming HTTP requests into data and\noutgoing data into HTTP responses.\n\nFugue provides a Twisted Web adapter in the form of an `IResource`_, the effect\nof this adapter is to act as a leaf resource—meaning Twisted performs no child\nresource lookups on it—that converts a Twisted Web request into a context map,\nexecutes an interceptor chain, and converts the context map back into something\nTwisted Web can respond to the request with.\n\nAn adapter has no formal structure since the coupling will depend on what is\nbeing adapted.\n\n.. _IResource: https://twistedmatrix.com/documents/current/api/twisted.web.resource.IResource.html\n\n\n-------\nExample\n-------\n\nA `basic HTTP API example`_ that returns a personal greeting based on a route:\n\n.. This should be a literal include, but those are prohibited by Github's\n   processors for security reasons.\n\n.. code-block:: python\n\n   from pyrsistent import m\n   from fugue.interceptors.http import route\n   from fugue.interceptors.http.route import GET\n   from fugue.adapters.twisted import twisted_adapter_resource\n   \n   \n   # Define a helper to construct HTTP 200 responses.\n   def ok(body):\n       return m(status=200, body=body.encode('utf-8'))\n   \n   # Define the handler behaviour.\n   def greet(request):\n       name = request['path_params']['name']\n       return ok(u'Hello, {}!'.format(name))\n   \n   # Declare the route.\n   interceptor = route.router(\n       (u'/greet/:name', GET, greet))\n   \n   # Create a Twisted Web resource that will execute the interceptor chain.\n   resource = twisted_adapter_resource([interceptor])\n   \nExecuting the example:\n\n.. code-block:: shell\n\n   # Run the script from a Fugue checkout.\n   $ twistd -n web --resource-script=examples/twisted_greet.py\n   # Use the service.\n   $ curl 'http://localhost:8080/greet/Bob'\n   Hello, Bob!\n\n.. _basic HTTP API example: https://github.com/jonathanj/fugue/blob/master/examples/twisted_greet.py\n\n\n------------\nInstallation\n------------\n\n.. code-block:: shell\n\n   pip install fugue\n\n\n------------\nContributing\n------------\n\nSee `CONTRIBUTING.rst`_.\n\n.. _CONTRIBUTING.rst: https://github.com/jonathanj/fugue/blob/master/CONTRIBUTING.rst\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathanj%2Ffugue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonathanj%2Ffugue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathanj%2Ffugue/lists"}