{ "info": { "author": "Martijn Faassen", "author_email": "faassen@startifact.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries" ], "description": "Traject\n*******\n\nIntroduction\n============\n\nIn web application construction there are two main ways to publish\nobjects to the web: routing and traversal. Both are a form of URL\ndispatch: in the end, a function or method is called as the result of\na pattern in the URL. Both use very different methods to do so,\nhowever.\n\nIn *routing* a mapping is made from URL patterns to controllers (or\nviews) that are called to generate the rendered web page. The URL\npattern is also used to pull parameter information from the URLs which\ncan then be passed on.\n\nTake for instance the URL ``departments/10/employees/17``. A URL\npattern could exist that maps all ``departments/*/employees/*``\npatterns to a particular callable. In addition, the routing system can\nbe used to declare that the parameters `10`` and ``17`` should be\ntaken from this URL and passed along as arguments to the\ncontroller. The programmer then programs the controller to retrieve\ncorrect models from the database using this information. After this\nthe controller uses the information in these models to construct the\ncontents of the view, for instance by rendering it with a HTML\ntemplate.\n\nIn *traversal*, there is no explicit mapping of URLs to controllers or\nviews. Instead a model structure is traversed step by step, guided by\nthe URL. By analogy one can in Python traverse through nested\ndictionaries (``d['a']['b']['c']``), or attributes (``d.a.b.c``). In\nthe end, a *view* is looked up for the final model that can be\ncalled. The view could be a special attribute on the model. More\nsophisticated systems can be used to separate the view from the model.\n\nThe URL ``departments/10/employees/17`` would be resolved to a view\nbecause there is a ``departments`` container model that contains\n``department`` model objects. In turn from a ``department`` model one\ncan traverse to the ``employees`` container, which in turn allows\ntraversal to individual employees, such as employee 17. In the end a\nview is looked up for employee 17, and called.\n\nRouting is often used in combination with a relational database,\ntypically exposed to objects by means of an object relation mapper.\nTraversal tends to be more convenient with in-memory object structures\nor object databases.\n\nRouting has advantages:\n\n* it is a good way for exposing relational content that doesn't have\n natural nesting.\n\n* the pattern registry gives the developer an explicit overview of the\n URL patterns in an application.\n\n* the approach is familiar as it is used by many frameworks.\n\nTraversal has advantages as well:\n\n* it is a good way for exposing object content that has arbitrary\n nesting.\n\n* model-driven: objects come equipped with their views. This allows\n the developer to compose an application from models, supporting a\n declarative approach.\n\n* location-aware: a nested object structure can easily be made\n location aware. Each model can know about its parent and its name in\n the URL. This allows for easy construction of URLs for arbitrary\n models. In addition, permissions can be declared based on this\n structure.\n\nTraject tries to combine the properties of routing and traversal in a\nsingle system. Traject:\n\n* looks like a routing system and has the familiarity of the routing\n approach.\n\n* works well for exposing relational models.\n\n* lets the developer explicitly declare URL mappings.\n\n* supports arbitrary nesting, as URL mappings can be nested and the\n system is also easily combined with normal traversal.\n\n* is model-driven. Routing is to models, not to views or controllers.\n\n* is location-aware. Models are in a nested structure and are aware of\n their parent and name, allowing model-based security declarations\n and easy URL construction for models.\n\nSome potential drawbacks of Traject are:\n\n* Traject expects a certain regularity in its patterns. It doesn't\n allow certain complex URL patterns where several variables are part\n of a single step (i.e ``foo/-``). Only a single\n variable is allowed per URL segment.\n\n* Traject needs to constructs or retrieve models for *each stage* in\n the route in order to construct a nested structure. This can mean\n more queries to the database per request. In practice this is often\n mitigated by the fact that the parent models in the structure are\n typically needed by the view logic anyway.\n\n* In Traject each model instance should have one and only one location\n in the URL structure. This allows not only URLs to be resolved to\n models, but also URLs to be generated for models. If you want the\n same model to be accessible through multiple URLs, you might have\n some difficulty.\n\nURL patterns\n============\n\nLet's consider an URL pattern string that is a series of steps\nseparated by slashes::\n\n >>> pattern_str = 'foo/bar/baz'\n\nWe can decompose it into its component steps using the ``parse``\nfunction::\n\n >>> import traject\n >>> traject.parse(pattern_str)\n ('foo', 'bar', 'baz')\n\nSteps may also be variables. A variable is a step that is prefixed by\nthe colon (``:``)::\n\n >>> traject.parse('foo/:a/baz')\n ('foo', ':a', 'baz')\n\nMore than one variable step is allowed in a pattern::\n\n >>> traject.parse('foo/:a/baz/:b')\n ('foo', ':a', 'baz', ':b')\n\nThe variable names in a pattern need to be unique::\n\n >>> traject.parse('foo/:a/baz/:a')\n Traceback (innermost last):\n ...\n ParseError: URL pattern contains multiple variables with name: a\n\nRegistering patterns\n====================\n\nIn Traject, the resolution of a URL path results in a model. This\nmodel can then in turn have views registered for it that allow this\nmodel to be displayed. How the view lookup works is up to the web\nframework itself.\n\nYou tell Traject which model is returned for which path by registering\na factory function per URL pattern. This factory function should\ncreate or retrieve the model object.\n\nThe factory function receives parameters for each of the matched\nvariables in whichever pattern matched - the signature of the factory\nfunction should include all the variables in the patterns that are\nmatched.\n\nLet's look at an example.\n\nThis is the URL pattern we want to recognize::\n\n >>> pattern_str = u'departments/:department_id/employees/:employee_id'\n\nWe can see two parameters in this URL pattern: `department_id`` and\n``customer_id``.\n\nWe now define a model as it might be stored in a database::\n\n >>> class Employee(object):\n ... def __init__(self, department_id, employee_id):\n ... self.department_id = department_id\n ... self.employee_id = employee_id\n ... def __repr__(self):\n ... return '' % (self.department_id, self.employee_id)\n\nWe define the factory function for this URL pattern so that an\ninstance of this model is returned. The parameters in this case would\nbe ``department_id`` and ``employee_id``::\n\n >>> def factory(department_id, employee_id): \n ... return Employee(department_id, employee_id)\n\nThe factory function in this case just creates a ``Employee`` object\non the fly. In the context of a relation database it could instead\nperform a database query based on the parameters supplied. If the\nfactory returns ``None``, this is interpreted as the system being\nunable to match the URL: the object cannot be found.\n\nIn order to register this factory function, we need a registry of\npatterns, so we'll create one::\n\n >>> patterns = traject.Patterns()\n\nPatterns need to be registered for particular classes or\n(``zope.interface``) interfaces. This is so that multiple pattern\nregistries can be supported, each associated with a particular root\nobject. In this case we'll register the patterns for a class\n``Root``::\n\n >>> class Root(object):\n ... pass\n\nWe can now register the URL pattern and the factory::\n\n >>> patterns.register(Root, pattern_str, factory)\n\nResolving a path\n================\n\nWe are ready to resolve paths. A path is part of a URL such as\n``foo/bar/baz``. It looks very much like a pattern, but all the\nvariables will have been filled in.\n\nThe models retrieved by resolving paths will be *located*. Ultimately\ntheir ancestor will be a particular root model from which all paths\nare resolved. The root model itself is not resolved by a pattern: it\nis the root from which all patterns are resolved.\n\nWe create a root model first::\n\n >>> root = Root()\n\nWhen a path is resolved, a complete chain of ancestors from model to\nroot is also created. It may be that no particular factory function\nwas registered for a particular path. In our current registry such\npatterns indeed exist: ``departments``, ``departments/:department_id``\nand ``departments/:department_id/employees`` all have no factory\nregistered.\n\nThese steps will have a *default* model registered for them instead.\nWhen resolving the pattern we need to supply a special default factory\nwhich will generate the default models when needed.\n\nLet's make a default factory here. The factory function needs to be\nable to deal with arbitrary keyword arguments as any number of\nparameters might be supplied::\n\n >>> class Default(object):\n ... def __init__(self, **kw):\n ... pass\n\nNow that we have a Default factory, we can try to resolve a path::\n \n >>> obj = patterns.resolve(root, u'departments/1/employees/2', Default)\n >>> obj\n \n >>> obj.department_id\n u'1'\n >>> obj.employee_id\n u'2'\n\nAn alternative ``resolve_stack`` method allows us to resolve a stack\nof names instead (where the first name to resolve is on the top of the\nstack)::\n\n >>> l = [u'departments', u'1', u'employees', u'2']\n >>> l.reverse()\n >>> patterns.resolve_stack(root, l, Default)\n \n\nConverters\n==========\n\nIt is possible to specify converters in patterns. A converter is a\nfunction that converts a value to a desired value, and raises a\nValueError if this is not possible. The build-in ``int`` in Python is\nan example of a converter.\n \nA converter is specified in a pattern with an extra colon and then a\nconverter identifier (``int`` in this case)::\n\n >>> pattern_str = u'individuals/:individual_id:int'\n\nTraject comes with a number of built-in converters:\n\n* ``unicode``: the default converter. Tries to convert the input to\n a unicode value. If no converter is specified, it will use this.\n\n* ``str``: tries to convert the input to a string.\n\n* ``int``: tries to convert the input to an integer.\n\n* ``unicodelist``: tries to convert the input to a list of unicode\n strings. The input is split on the ``;`` character.\n\n* ``strlist``: tries to convert the input to a list of strings. The\n input is split on the ``;`` character.\n\n* ``intlist``: tries to convert the input to a list of integers. The\n input is split on the ``;`` character.\n\nWe now register the pattern::\n\n >>> class Individual(object):\n ... def __init__(self, individual_id):\n ... self.individual_id = individual_id\n ... def __repr__(self):\n ... return '' % self.individual_id\n >>> patterns.register(Root, pattern_str, Individual)\n\n >>> indiv = patterns.resolve(root, u'individuals/1', Default)\n\nWe see that the value has indeed been converted to an integer::\n\n >>> indiv.individual_id\n 1\n\nNew converters can be registered using the ``register_converter``\nmethod. This method takes two arguments: the converter name, and the\nconverter function. The converter function should take a single\nargument and convert it to the desired value. If conversion fails, a\n``ValueError`` should be raised. The Python ``int`` function is an\nexample of a valid converter.\n\nLocations\n=========\n\nTraject supports the notion of locations. After we find a model, the\nmodel receive two special attributes::\n\n* ``__name__``: the name we addressed this object with in the path.\n\n* ``__parent__``: the parent of the model. This is an model that\n matches the parent path (the path without the last step).\n\nThe parent will in turn have a parent as well, all the way up to the\nultimate ancestor, the root.\n\nWe can look at the object we retrieved before to demonstrate the\nancestor chain::\n\n >>> obj.__name__\n u'2'\n >>> isinstance(obj, Employee)\n True\n >>> p1 = obj.__parent__\n >>> p1.__name__ \n u'employees'\n >>> isinstance(p1, Default)\n True\n >>> p2 = p1.__parent__\n >>> p2.__name__\n u'1'\n >>> isinstance(p2, Default)\n True\n >>> p3 = p2.__parent__\n >>> p3.__name__\n u'departments'\n >>> isinstance(p3, Default)\n True\n >>> p3.__parent__ is root\n True\n\nDefault objects have been created for each step along the way, up\nuntil the root.\n\nConsuming a path\n================\n\nIn a mixed traject/traversal environment, for instance where view\nlookup is done by traversal, it can be useful to be able to resolve a\npath according to the patterns registered until no longer\npossible. The rest of the the steps are not followed, and are assumed\nto be consumed in some other way using the traversal system.\n\nThe ``consume`` method will consume steps as far as possible, return\nthe steps that weren't consumed yet, those steps that were consumed,\nand the object it managed to find::\n\n >>> unconsumed, consumed, last_obj = patterns.consume(root, \n ... 'departments/1/some_view', Default)\n\n``departments/1/some_view`` cannot be consumed fully by the pattern\n``departments/:department_id/employees/:employee_id``, as\n``some_view`` does not match the expected ``employees``.\n\nWe can see which parts of the path could not be consumed::\n\n >>> unconsumed\n ['some_view']\n\nAnd which parts of the path were consumed as part of a pattern::\n\n >>> consumed\n ['departments', '1']\n\nThe last object we managed to consume stands for ``1``::\n\n >>> isinstance(last_obj, Default)\n True\n >>> last_obj.__name__\n '1'\n >>> p1 = last_obj.__parent__ \n >>> p1.__name__\n 'departments'\n >>> p1.__parent__ is root\n True\n\nThe method ``consume_stack`` does the same with a stack::\n\n >>> l = ['departments', '1', 'some_view']\n >>> l.reverse()\n >>> unconsumed, consumed, last_obj = patterns.consume_stack(root, l, Default)\n >>> unconsumed\n ['some_view']\n >>> consumed\n ['departments', '1']\n >>> isinstance(last_obj, Default)\n True\n >>> last_obj.__name__\n '1'\n >>> p1 = last_obj.__parent__ \n >>> p1.__name__\n 'departments'\n >>> p1.__parent__ is root\n True\n\nGiving a model its location\n===========================\n\nModels are automatically given their location after traversal. There\nis however another case where giving an object a location can be\nuseful. We may for instance retrieve an object from a query and then\nwish to construct a URL to it, or check whether it has\nlocation-dependent permissions. Traject therefore also offers\nfunctionality to reconstruct an object's location.\n\nIn order to do this, we need to register a special function per model\nclass that is the inverse of the factory. Given a model instance, it\nneeds to return the arguments used in the pattern. Thus, for the\nfollowing pattern::\n\n >>> pattern_str = u'departments/:department_id/employees/:employee_id'\n\nand a given model, we would need to reconstruct the arguments\n``department_id`` and ``employee_id`` from that model.\n\nThis is a function that does this for ``Employee``::\n\n >>> def employee_arguments(obj):\n ... return {'employee_id': obj.employee_id, \n ... 'department_id': obj.department_id} \n\n\nWhen we register it, we also need to supply the class for which it can\nreconstruct the arguments, in this case, ``Employee``::\n\n >>> patterns.register_inverse(Root, Employee, pattern_str, employee_arguments)\n\nLet's construct some employee now::\n\n >>> m = Employee(u'13', u'27')\n\nIt has no location (no ``__name__`` or ``__parent__``)::\n\n >>> m.__name__\n Traceback (most recent call last):\n ...\n AttributeError: ...\n\n >>> m.__parent__\n Traceback (most recent call last):\n ...\n AttributeError: ...\n\nWe can now use the ``locate`` method to locate it::\n\n >>> patterns.locate(root, m, Default)\n\nThe model will now have ``__name__`` and ``__parent__`` attributes::\n\n >>> m.__name__\n u'27'\n >>> p1 = m.__parent__\n >>> p1.__name__\n u'employees'\n >>> p2 = p1.__parent__\n >>> p2.__name__\n u'13'\n >>> p3 = p2.__parent__\n >>> p3.__name__\n u'departments'\n >>> p3.__parent__ is root\n True\n\nA global patterns registry\n==========================\n\nSince the patterns registry is clever enough to distinguish between\nroots, in many scenarios only a single, global ``Patterns`` registry\nis needed. Top-level functions have been made available in the\n``traject`` namespace to manipulate and use this patterns registry::\n\n traject.register\n traject.register_inverse\n traject.register_converter\n traject.resolve\n traject.resolve_stack\n traject.consume\n traject.consume_stack\n traject.locate\n\nCHANGES\n*******\n\n0.10.1 (2010-01-18)\n===================\n\n- Fix bug in converter logic: it broke in certain cases\n when traject.locate was used.\n\n0.10 (2009-11-26)\n=================\n\n- Add converter functionality for path segments. This makes it\n possible to ensure that a path segment is of a certain type, such as\n an integer, using ``a/:id:int``.\n\n Converters ``unicode``, ``str``, ``int``, ```unicodelist``,\n `strlist`` and ``intlist`` are known. ``unicode`` is the default\n converter used if no converter was specified. New converters can be\n registered using ``register_converter``. If a converter raises a\n ValueError, the path cannot be resolved.\n\n0.9 (2009-11-16)\n================\n\n- Initial public release.\n\n\nDownload\n********", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "UNKNOWN", "keywords": "route routing url traverse traversing web", "license": "ZPL", "maintainer": null, "maintainer_email": null, "name": "traject", "package_url": "https://pypi.org/project/traject/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/traject/", "project_urls": { "Download": "UNKNOWN", "Homepage": "UNKNOWN" }, "release_url": "https://pypi.org/project/traject/0.10.1/", "requires_dist": null, "requires_python": null, "summary": "A URL dispatch to object system that combines aspects of routing and traversal.", "version": "0.10.1" }, "last_serial": 800865, "releases": { "0.10": [ { "comment_text": "", "digests": { "md5": "5580c80b72c6d119c89c0c9958d430a6", "sha256": "94ed525b65f60e6ffa2475c537dd79dc2a17f964ebf08a963e3cf048f80c51e5" }, "downloads": -1, "filename": "traject-0.10.tar.gz", "has_sig": false, "md5_digest": "5580c80b72c6d119c89c0c9958d430a6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24369, "upload_time": "2009-11-26T14:28:17", "url": "https://files.pythonhosted.org/packages/3a/3d/f6d80b8d0232bab745f3d9e224ee777a34b3724084bc49a3131ca763da30/traject-0.10.tar.gz" } ], "0.10.1": [ { "comment_text": "", "digests": { "md5": "2fcd088044caa8e9f6397b28abbd3d19", "sha256": "062489968803b6403de6158be399ca02600db6c11da1437eb639bce9be5d4897" }, "downloads": -1, "filename": "traject-0.10.1.tar.gz", "has_sig": false, "md5_digest": "2fcd088044caa8e9f6397b28abbd3d19", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24563, "upload_time": "2010-01-18T22:06:01", "url": "https://files.pythonhosted.org/packages/4a/ee/381f98027f67fc68e1947df9fbfbfefd998a9a9389aa29da4bf47858152f/traject-0.10.1.tar.gz" } ], "0.9": [ { "comment_text": "", "digests": { "md5": "667f56065eb5cff14149a464334429dd", "sha256": "7094d5cadb657c3336b86e58ba7e19e941e88f9293c3489e26b2a6af1ee7ba40" }, "downloads": -1, "filename": "traject-0.9.tar.gz", "has_sig": false, "md5_digest": "667f56065eb5cff14149a464334429dd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21770, "upload_time": "2009-11-16T12:45:51", "url": "https://files.pythonhosted.org/packages/94/7e/6e4719fe138143cb560806a1f68ab5923acbacb9b11034f4ce2b09b3cfbd/traject-0.9.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "2fcd088044caa8e9f6397b28abbd3d19", "sha256": "062489968803b6403de6158be399ca02600db6c11da1437eb639bce9be5d4897" }, "downloads": -1, "filename": "traject-0.10.1.tar.gz", "has_sig": false, "md5_digest": "2fcd088044caa8e9f6397b28abbd3d19", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24563, "upload_time": "2010-01-18T22:06:01", "url": "https://files.pythonhosted.org/packages/4a/ee/381f98027f67fc68e1947df9fbfbfefd998a9a9389aa29da4bf47858152f/traject-0.10.1.tar.gz" } ] }