xrtr
¶
A generic string router based on a Radix Tree structure, (partially) Cython optimized for speed.
Documentation¶
License¶
xrtr
is a free software distributed under the MIT license, the same license as Router’s license.
To Do¶
- There is a LOT of room for improvement (specially when migrating the code to C and Cython and the fact this is my first project with Cython);
- Fix test coverage (and why is it not covering method declarations, as an example);
- There is a lot of fixes to be done regarding Cython, distribution, naming conventions and so on;
- Add Windows builds using AppVeyor;
Installing¶
To install xrtr
, use pip
(or your favorite Python package manager), like:
pip install xrtr
And you’re ready to the next step. Hooray!
Warning
At this time, xrtr
binary packages are only available for Linux. Other platforms will be added in the future. PRs are welcome!
Using xrtr
¶
xrtr
is a fast (and will be faster) path router for Python written in Cython, primarily for web frameworks but it’s flexible to be used with other conventions as well. It is based on a compressed dynamic trie (radix tree) structure for efficient matching, with support for variables, endpoints, layered middlewares (or any other objects) and very fast speeds provided by a Cython optimization of the tree.
The tree¶
To use the xrtr
router, you simply need to instantiate the RadixTree
object:
>>> from xrtr import RadixTree
>>> tree = RadixTree()
Now, let’s say you have an endpoint (object, function) to be run without variables:
>>> def myendpoint():
... return "hello, world"
>>> tree.insert("/", myendpoint, ["GET"])
>>> tree.get("/", "GET")
(<function __main__.myendpoint()>, [], {})
If you want to grab variables, you can choose to get a variable between the separator
(defaults to /
) or glob everything that comes after.
To create a simple variable, use a colon :
as identifier:
>>> tree.insert("/foo/:bar", myendpoint, ["GET"])
>>> tree.get("/foo/hello", "GET")
(<function __main__.myendpoint()>, [], {'bar': 'hello'})
If you want to glob everything in a variable, use an asterisk *
as identifier:
>>> tree.insert("/static/*path", myendpoint, ["GET"])
>>> tree.get("/static/my/file/may/be/somewhere.py", "GET")
(<function __main__.myendpoint()>, [], {'path': 'my/file/may/be/somewhere.py'})
Note
You may have already noticed that the method RadixTree.get
returns a tuple
of three objects: the endpoint (or None), a list of middlewares (see the next chapter) and a dictionary of the variables and its values (if any matches). In case of no matches or an endpoint object / function is not found, the default return will be None, [], {}
.
Middlewares¶
Sometimes, you want to add a function or, most commonly, a middleware to be executed before or after a request, to any or certain points. Luckily, to execute a middleware in certain points may be hard on most solutions, either leaving you without any alternatives to implement one or based on subclassing views, routes, handlers, you name it. xrtr
aims to make this very, very simple.
Nothing better than a simple example:
>>> def mymiddleware():
... return "hello, middle world"
>>> tree.insert("/foo", mymiddleware, ["GET"], no_conflict=True)
>>> tree.get("/foo/hello", "GET")
(<function __main__.myendpoint()>, [<function __main__.mymiddleware()>], {'bar': 'hello'})
Those middlewares can be stacked without replication and, again, can be a function, object, anything you find appropriate. Just don’t forget to add the keyword no_conflict
to True
when invoking the RadixTree.insert
method.
Configuring identifiers¶
In case you’re wondering: “another path based router?”, don’t worry: the separator
(defaults to /
) and variable
(defaults to :
) can be configurable (as long as they’re punctuations). The glob identifier (defaults to *
) is not configurable.
There are two ways of changing the identifiers: using the class constructor or changing them at runtime.
Warning
You can only change the identifiers prior to inserting routes, otherwise it raises ValueError
.
Changing at runtime¶
>>> from xrtr import RadixTree
>>> tree = RadixTree()
>>> tree.SEPARATOR = "."
>>> tree.SEPARATOR
'.'
>>> tree.VARIABLE = "$"
>>> tree.VARIABLE
'$'
>>> tree.insert(".foo.$bar", object, ["FOO"])
>>> tree.get(".foo.hello", "FOO")
(object, [], {'bar': 'hello'})
>>> tree.config
{'variable': '$', 'separator': '.'}
Using the constructor¶
>>> from xrtr import RadixTree
>>> tree = RadixTree(separator=".", variable="$")
>>> tree.SEPARATOR
'.'
>>> tree.VARIABLE
'$'
>>> tree.insert(".foo.$bar", object, ["FOO"])
>>> tree.get(".foo.hello", "FOO")
(object, [], {'bar': 'hello'})
>>> tree.config
{'variable': '$', 'separator': '.'}
Method utilities¶
Starting with xrtr
0.2.0, some changes were required to quickly identify if the given route
does in fact exists, but the requested method
is not available. Enter in scene: the sentinel
object.
Sentinel object¶
Everytime you search for a route and its specific method, sometimes the route even exists (let’s say, /foo
), but the requested method doesn’t (GET
exists, OPTIONS
don’t). This can be quickly checked against the sentinel
object (or property, in xrtr
case):
>>> from xrtr import RadixTree
>>> tree = RadixTree()
>>> tree.insert("/foo", some_endpoint, ["GET"])
>>> handler, middlewares, params = tree.get("/foo", "OPTIONS")
>>> handler is tree.sentinel
True
This way, it is simple to deal with more fine grained errors, such as HTTP 405
.
Available methods¶
In case you need just to get the available methods of one simple endpoint (for informational purposes), you can perform that by using the methods_for
method:
>>> from xrtr import RadixTree
>>> tree = RadixTree()
>>> tree.insert("/foo", some_endpoint, ["GET"])
>>> tree.methods_for("/foo")
{'GET'}
Contributing¶
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
Bug reports¶
When reporting a bug please include:
- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.
Documentation improvements¶
xrtr could always use more documentation, whether as part of the official xrtr docs, in docstrings, or even on the web in blog posts, articles, and such.
Feature requests and feedback¶
The best way to send feedback is to file an issue at https://github.com/vltr/xrtr/issues.
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that code contributions are welcome :)
Development¶
To set up xrtr
for local development:
Fork xrtr (look for the “Fork” button).
Clone your fork locally:
git clone git@github.com:your_name_here/xrtr.git
Create a branch for local development:
git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you’re done making changes, run all the checks, doc builder and spell checker with tox one command:
tox
Commit your changes and push your branch to GitHub:
git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature
Submit a pull request through the GitHub website.
Pull Request Guidelines¶
If you need some code review or feedback while you’re developing the code just make the pull request.
For merging, you should:
- Include passing tests (run
tox
) [1]. - Update documentation when there’s new API, functionality etc.
- Add a note to
CHANGELOG.rst
about the changes. - Add yourself to
AUTHORS.rst
.
[1] | If you don’t have all the necessary python versions available locally you can rely on Travis - it will run the tests for each change you add in the pull request. It will be slower though … |
Tips¶
To run a subset of tests:
tox -e envname -- pytest -k test_myfeature
To run all the test environments in parallel (you need to pip install detox
):
detox
Changelog¶
v0.2.1 on 2018-10-09¶
- Fixed bug where the
no_conflict
flag where not being propagated to theadd_method
if a “non-conflicting method” was the first node being created in that tree.
v0.2.0 on 2018-10-03¶
- Add
method_for
function.
v0.1.4 on 2018-10-03¶
- Add
sentinel
object.
v0.1.3 on 2018-09-21¶
- Add testing for repeated variables.
v0.1.2 on 2018-09-14¶
- Minor tweaks and improvements (search optimizations).
v0.1.1 on 2018-09-11¶
- Minor tweaks and improvements.
v0.1.0 on 2018-08-22¶
- First release on PyPI.
Authors¶
- Richard Kuesters - https://vltr.github.io/