{"id":16949747,"url":"https://github.com/tailhook/aio-routes","last_synced_at":"2025-04-11T20:35:41.010Z","repository":{"id":17062347,"uuid":"19827050","full_name":"tailhook/aio-routes","owner":"tailhook","description":"URL routing library for asyncio","archived":false,"fork":false,"pushed_at":"2014-12-12T16:27:34.000Z","size":257,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-25T16:45:01.276Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"tstranex/u2f","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tailhook.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-15T16:35:09.000Z","updated_at":"2017-01-30T12:52:21.000Z","dependencies_parsed_at":"2022-09-26T21:12:08.980Z","dependency_job_id":null,"html_url":"https://github.com/tailhook/aio-routes","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Faio-routes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Faio-routes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Faio-routes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Faio-routes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tailhook","download_url":"https://codeload.github.com/tailhook/aio-routes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248173810,"owners_count":21059594,"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":[],"created_at":"2024-10-13T21:55:58.173Z","updated_at":"2025-04-11T20:35:40.985Z","avatar_url":"https://github.com/tailhook.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"==========\naio-routes\n==========\n\nAio-routes is a URL routing library for web applications. It doesn't support\ntypical pattern-based or regular-expression bases routing. But rather it\ntraverses objects while resolving an url. See examples below, for more info\n\nAioroutes works not only for HTTP but for any kind of RPC, for example for\nmethod invocation over WebSockets. HTTP support is built-in, for other kinds\nof things small pieces of glue code is needed.\n\n\nUsage\n=====\n\nThere are two basic classes:\n\n* ``Site`` represents top-level entity, containing list of the resources\n\n* ``Resource`` is a point in hierarchy of url resolving\n\nBasically you have a single ``Site`` instance, which you create in ``main``\nfunction of the application, and multiple resources which are \"mounted\" into\nthe site hierarchy.\n\nLet's see an example:\n\n.. code-block:: python\n\n    class Root(aioroutes.Resource):\n\n        @aioroutes.page\n        def some_path(self):\n            return \"hello\"\n\n    @asyncio.coroutine\n    def main():\n        site = aioroutes.Site(resources=[Root()])\n        serv = yield from asyncio.get_event_loop().create_server(\n            partial(HttpProto, site), port=8000)\n        print(\"Listening on http://localhost:8000\")\n        yield from serv.wait_closed()\n\n\n    if __name__ == '__main__':\n        asyncio.get_event_loop().run_until_complete(main())\n\n\nNow if you go to ``http://localhost:8000/some_path`` you will see ``hello``.\nIn the next examples we will avoid ``main`` boilerplate.\n\n.. note:: We don't use this property now, but ``@aioroutes.page`` makes a\n   function a coroutine (just like ``@asyncio.coroutine`` do), so you can do\n   some blocking things in that function\n\nYou may noticed, that home page ``http://localhost:8000`` is empty. To fill\nin that page, you need to add a special method ``index``:\n\n.. code-block:: python\n\n    class Root(aioroutes.Resource):\n\n        @aioroutes.page\n        def index(self):\n            return \"home page\"\n\n\nParameters\n==========\n\nIf your page needs any parameters, you can just add them to a method:\n\n.. code-block:: python\n\n        @aioroutes.page\n        def hello(self, name):\n            return \"Hello {}!\".format(name)\n\nIn this case you may visit a page by any of the following urls::\n\n    http://localhost:8000/hello/John\n    http://localhost:8000/hello?name=John\n\nSubmitting arguments as urlencoded ``POST`` form works too.\n\nThe usual semantics for python arguments are observed. For example you can\nmake keyword-only arguments and you may have defaults:\n\n.. code-block:: python\n\n        @aioroutes.page\n        def hello(self, *, name=\"World\"):\n            return \"Hello {}!\".format(name)\n\nYou may also use annotations to make aguments typed:\n\n.. code-block:: python\n\n        @aioroutes.page\n        def add(self, left: int, right: int):\n            return str(left + right)\n\nAny function that raises ``ValueError`` when input is wrong, can be used as a\nvalidator. I.e. it may be ``json.loads`` or the contract from trafaret_ library\n\n.. note:: If arguments are not validated a 404 page is returned. It matches\n   the common case where ``/forum/some_crap`` is looked for instead of\n   ``/forum?topic=123``. But it's not suitable for form validation (unless you\n   do it on javascript-side). See recipe below for forms.\n\n\nChild Resources\n===============\n\nMultiple (sub)applications can be combined in two ways:\n\n1. By \"mounting\" the application in url hierarchy.\n2. By supplying multiple resources in ``Site`` constructor\n\nThe first option is used most of the time. Let's take an example. Let's\npretend we have two applications:\n\n.. code-block:: python\n\n    class Forum(aioroutes.Resource):\n\n        @aioroutes.page\n        def index(self):\n            return 'topics'\n\n        @aioroutes.page\n        def topic(self, topic:int):\n            return 'topic: {}'.format(topic)\n\n    class News(aioroutes.Resource):\n\n        @aioroutes.page\n        def index(self):\n            return 'all_news'\n\n        @aioroutes.page\n        def article(self, slug:str):\n            return 'article: {}.format(slug)\n\nNow, we can combine them in two ways:\n\n.. code-block:: python\n\n    class Root(aioroutes.Resource):\n        forum = Forum()\n        news = News()\n\nThen pages will be accessible with the following urls::\n\n    http://localhost:8000/forum/\n    http://localhost:8000/forum/topic/1234\n    http://localhost:8000/news/article/something\n\nIf you would combine them at the site level::\n\n    site = aioroutes.Site(resources=[Forum(), News()])\n\nYou will get the following urls working::\n\n    http://localhost:8000/ -\u003e forum\n    http://localhost:8000/topic/1234\n    http://localhost:8000/article/something\n\nThe semantics are exactly the following. Given the first resource, try to\nresolve URL. If that resolves, return a page. If that raises ``NotFound``\n(equivalent of 404 page), try next resource. So which page is served depends\non order of resources specified. In general this way is ''not recommended''.\n\n\nIndex and Default\n=================\n\nThere are two special methods in resolve chain:\n\n* ``index`` -- called when no more path pieces follows\n\n* ``default`` -- called when more path pieces exists, but no apropriate\n  method found.\n\nNote, that form arguments can be used in both ``index`` and ``default``\nmethods but ``index`` never receives positional arguments, while ``default``\nalways has at least one.\n\nAlso ``default`` method can return a ``Resource`` (hence might be decorated\nwith ``@resource``), while ``index`` method must always be a ``page``.\n\n\nStickers\n========\n\nTBD\n\n\nResolvers\n=========\n\nTBD\n\n\nDynamic Resources\n=================\n\nTBD\n\n\nDecorators\n==========\n\nTBD\n\n\nExceptions\n==========\n\nTBD\n\nRecipes\n=======\n\n\nTemplates\n---------\n\nA typical template wrapper (using jinja as an example):\n\n.. code-block:: python\n\n    def template(name):\n        def wrapper(fun):\n            @web.postprocessor(fun)\n            def template_postprocessor(self, resolver, data):\n                if not isinstance(data, dict):\n                    return data\n                data = data.copy()\n                data.update({\n                    # Some common template context\n                })\n                template = self.jinja.get_template(name + '.html')\n                return template.render(data)\n            return template_postprocessor\n        return wrapper\n\nIt can be used as:\n\n.. code-block:: python\n\n   @template('mypages/cool_page')\n   def cool_page(self, value=1):\n       return {'value: 1}\n\nThings to note:\n\n#. If method returns not a dict, just pass it through. It's useful for error\n   handling and other things.\n\n#. We assume that there is a jinja environment in the class,\n   named``self.jinja``. You can use global environment here, but better to\n   use some dependency injection framework to have jinja environment in the\n   instance.  Syntax for other templating may vary.\n\n#. ``date.update`` is for things that are local for request, totally global\n   things may go into environment. However, if you like to share template\n   ''environment'' (in jinja dialect) with multiple applications, you might\n   want to put globals here. (However, as apps have different template\n   decorator, they might use different environment too).\n\n\nForms\n-----\n\nTBD\n\n\nStatic Resource\n===============\n\nThere is a built-in resource that returns static files. It's very dumb and\nugly so, use it only for development. Example:\n\n.. code-block:: python\n\n    from aioroutes.static import StaticResource\n    static = StaticResource('./public', ['js', 'css'])\n    resources = [Root()]\n    if options.standalone_debugging_server:\n        resources.insert(0, static)\n    site = Site(resources=[static, Root()])\n\nIf you omit second parameter to ``StaticResource`` then it will serve all\ndirectories, not just ``/js`` and ``/css`` as in example.\n\nYou may also \"mount\" static resource at arbitrary point in the tree, just like\nany other resource.\n\n\nBeyond HTTP\n===========\n\nTBD\n\n\nHistory\n=======\n\nThe library was ininitally named ``zorro.web`` and was a part of zorro_\nnetworking library.\n\n.. _zorro: http://github.com/tailhook/zorro\n.. _trafaret: http://github.com/Deepwalker/trafaret\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftailhook%2Faio-routes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftailhook%2Faio-routes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftailhook%2Faio-routes/lists"}