firmant.routing

This module handles mapping between a set of attributes and a URL/document.

A URLMapper serves as a means of converting a set of key-value pairs into a full URL string. Each instance of URLMapper has a list of objects that provide the AbstractPath interface. When querying URLMapper.url(), or URLMapper.path(), the URLMapper finds a path object for which the provided attributes exactly cover the attributes of the path. Additionally, any bound variables must match the values provided in the query.

Note

Matching of attributes to URLs is designed to be similar to unification as found in Prolog, first order logic, and the lambda calculus; however, the similarity should not be read into too deeply as it may not be adhered to in the future.

Modules in this package:

components

Functions

firmant.routing.class_name(cls)

The string representation of a class’s name.

For example if we define the class Foo, the full name of the class is firmant.utils.Foo. class_name() will return this as a string.

>>> class Foo(object): pass
...
>>> class_name(Foo)
'firmant.utils.Foo'

If an object that is not a class is passed to class_name(), then a TypeError will be raised.

>>> class_name('Foo')
Traceback (most recent call last):
TypeError: `cls` does not name a type.
firmant.routing.merge_dicts(a, *args)

Merge two dictionaries in a manner that doesn’t lose information.

One or two dictionaries with disjoint sets of keys will merge with the same rules as the update() function for dictionaries.

>>> pprint(merge_dicts({'a': 1}))
{'a': 1}
>>> pprint(merge_dicts({'a': 1}, {'b': 2}))
{'a': 1, 'b': 2}

If two keys have the same value, the merge happens cleanly.

>>> pprint(merge_dicts({'a': 1, 'b': 2}, {'b': 2}))
{'a': 1, 'b': 2}

An arbitrary number of dictionaries may be merged using merge_dicts().

>>> pprint(merge_dicts({'a': 1, 'b': 2}, {'b': 2}, {'c':3}))
{'a': 1, 'b': 2, 'c': 3}

If two instances have the same key, but different values, a ValueError will be raised.

>>> pprint(merge_dicts({'a': 1, 'b': 2}, {'b': 3}))
Traceback (most recent call last):
ValueError: Conflicting values for 'b'

Classes

class firmant.routing.AbstractPath

Bases: object

The interface for all path objects.

The URLMapper assumes all objects provided to it implement this interface.

Intuitively, a path is simply a string that is defined by a set of key-value pairs. The attributes property is the set of keys that define the path.

The following properties must be true in any valid path object:

  • attributes = union(bound_attributes, free_attributes)
  • attributes - free_attributes = bound_attributes
  • attributes - bound_attributes = free_attributes
attributes
All attributes that define the string representation of the path.
bound_attributes

Attributes with a fixed value.

In order for a set of values to unify with this path, the keys and value of bound_attributes must match those specified in the query.

construct(*args, **kwargs)

Use the values given in kwargs to construct the string representation of the path.

The values in kwargs are not required to be any type, but should be of a type the path may safely convert to a string.

free_attributes

Attributes with no value.

These attributes may be any value and still match the URL.

match(*args, **kwargs)

True if and only if kwargs and args specify the correct set of attributes.

Each value in args should be a dictionary. Provided keys must exactly cover all attributes and provided values must not conflict with bound attributes. If the cover is not exact, or there are conflicting values, the result of match() is False.

class firmant.routing.BoundNullPathComponent(attribute, value)

Bases: firmant.routing.AbstractPath

A mapper between a single attribute and the empty string.

Each instance will match exactly one attribute. Construct will always return None if there is a match.

>>> bnpc = BoundNullPathComponent('month', 3)
>>> bnpc.attributes
set(['month'])
>>> bnpc.bound_attributes
{'month': 3}
>>> bnpc.free_attributes
set([])
>>> bnpc.match({'month': 3})
True
>>> bnpc.match(month=3)
True
>>> bnpc.construct({'month': 3}) is None
True
>>> bnpc.construct(month=3) is None
True

It is an error to provide the same attribute twice with two different values:

>>> bnpc.match({'month': 2}, month=3)
Traceback (most recent call last):
ValueError: Conflicting values for 'month'

Note that the same attribute can be specified multiple times if the values are equal:

>>> bnpc.match({'month': 3}, month=3)
True

If attributes not matching the single attribute are specified, then a ValueError will be thrown.

>>> bnpc.construct(month=3, year=10)
Traceback (most recent call last):
ValueError: Attributes do not match path
>>> bnpc.construct(year=10)
Traceback (most recent call last):
ValueError: Attributes do not match path
>>> bnpc.construct()
Traceback (most recent call last):
ValueError: Attributes do not match path
attributes
The set of attributes contains the single attribute specified at creation time.
bound_attributes
The single attribute is bound to the value specified.
construct(*args, **kwargs)
This will return None if the attributes match, or raise a ValueError otherwise.
free_attributes

Attributes with no value.

These attributes may be any value and still match the URL.

match(*args, **kwargs)

True if and only if kwargs and args specify the correct set of attributes.

Each value in args should be a dictionary. Provided keys must exactly cover all attributes and provided values must not conflict with bound attributes. If the cover is not exact, or there are conflicting values, the result of match() is False.

class firmant.routing.CompoundComponent(*args)

Bases: firmant.routing.AbstractPath

A path object formed by joining two or more path objects with ‘/’.

A CompoundComponent is transparently formed by dividing path objects.

>>> tags = StaticPathComponent('tags')
>>> type = BoundNullPathComponent('type', 'tag')
>>> slug = SinglePathComponent('slug', str)
>>> path = type/tags/slug
>>> path 
<firmant.routing.CompoundComponent object at 0x...>
>>> path.match(type='tag', slug='foobar')
True
>>> path.match(type='tag')
False
>>> path.match(slug='foobar')
False
>>> path.construct(type='tag', slug='foobar')
'tags/foobar'

If construct() is called with attributes that do not match the path, then a ValueError will be thrown.

>>> path.construct(type='tag')
Traceback (most recent call last):
ValueError: Attributes do not match path
attributes
The attributes are the union of all components’ attributes.
bound_attributes
The attributes are the union of all components’ bound_attributes.
construct(*args, **kwargs)
Construct each path component, and join with ‘/’.
free_attributes

Attributes with no value.

These attributes may be any value and still match the URL.

match(*args, **kwargs)

True if and only if kwargs and args specify the correct set of attributes.

Each value in args should be a dictionary. Provided keys must exactly cover all attributes and provided values must not conflict with bound attributes. If the cover is not exact, or there are conflicting values, the result of match() is False.

class firmant.routing.SinglePathComponent(attribute, conv=<function <lambda> at 0x94ab87c>)

Bases: firmant.routing.AbstractPath

A mapper between a single attribute and its string representation.

Each instance will match exactly one attribute. At creation time, a conversion function conv is specified that will be called to obtain the string representation of the attribute.

In this example, a component with the attribute month is created. When the construct() method is called, it returns a two-digit representation of the number.

>>> spc = SinglePathComponent('month', lambda m: '%02i' % m)
>>> spc.attributes
set(['month'])
>>> spc.bound_attributes
{}
>>> spc.free_attributes
set(['month'])
>>> spc.match({'month': 3})
True
>>> spc.match(month=3)
True
>>> spc.match(year=2010)
False
>>> spc.match(year=2010, month=3)
False
>>> spc.construct({'month': 3})
'03'
>>> spc.construct(month=3)
'03'

It is an error to provide the same attribute twice with two different values:

>>> spc.match({'month': 2}, month=3)
Traceback (most recent call last):
ValueError: Conflicting values for 'month'

Note that the same attribute can be specified multiple times if the values are equal:

>>> spc.match({'month': 3}, month=3)
True

If attributes not matching the single attribute are specified, then a ValueError will be thrown.

>>> spc.construct(month=3, year=10)
Traceback (most recent call last):
ValueError: Attributes do not match path
>>> spc.construct(year=10)
Traceback (most recent call last):
ValueError: Attributes do not match path
>>> spc.construct()
Traceback (most recent call last):
ValueError: Attributes do not match path
attributes
The set of attributes contains the single attribute specified at creation time.
bound_attributes
No attributes are bound. This will always be an empty dictionary.
construct(*args, **kwargs)

Create the string representation of the path.

The value of the attribute will be passed through the conversion function. The conversion function may return None.

free_attributes

Attributes with no value.

These attributes may be any value and still match the URL.

match(*args, **kwargs)

True if and only if kwargs and args specify the correct set of attributes.

Each value in args should be a dictionary. Provided keys must exactly cover all attributes and provided values must not conflict with bound attributes. If the cover is not exact, or there are conflicting values, the result of match() is False.

class firmant.routing.StaticPathComponent(path)

Bases: firmant.routing.AbstractPath

A path component with no attributes that creates a static string.

In this example, a path component is created that will construct to the path images:

>>> spc = StaticPathComponent('images')
>>> spc.attributes
set([])
>>> spc.bound_attributes
{}
>>> spc.free_attributes
set([])
>>> spc.match({'month': 3})
False
>>> spc.match()
True
>>> spc.construct()
'images'

It is an error to specify any attributes when constructing:

>>> spc.construct(month=2)
Traceback (most recent call last):
ValueError: Do not specify attributes for StaticPathComponent
attributes

By definition, a StaticPathComponent has no attributes.

This will return the empty set.

bound_attributes

By definition, a StaticPathComponent has no attributes.

This will return an empty dictionary.

construct(*args, **kwargs)
The constructed path is always the string constant specified.
free_attributes

Attributes with no value.

These attributes may be any value and still match the URL.

match(*args, **kwargs)

True if and only if kwargs and args specify the correct set of attributes.

Each value in args should be a dictionary. Provided keys must exactly cover all attributes and provided values must not conflict with bound attributes. If the cover is not exact, or there are conflicting values, the result of match() is False.

class firmant.routing.URLMapper(output_dir, url_root, urls=None)

Bases: object

Find the url or filesystem path that correlate with a set of attributes.

The distinction between urls and paths is best described by example. Let’s declare the attributes slug='foo' and type='object'. The path on the filesystem, where the output would be written could be /path/to/output/directory/objects/foo/index.html while the URL where the document is accessible would be http://permanent.url/objects/foo/.

Having the URLMapper handle the logic of both paths and URLs makes sense. Consider a case where the user wishes to have the above URL be http://permanent.url/objects/foo.html. The local filesystem path would need to adapt to /path/to/output/directory/objects/foo.html

Creating a URLMapper is simple:

>>> from firmant.routing.components import *
>>> um = URLMapper('/path/to/output/directory', 'http://permanent.url/')
>>> um.add( TYPE('post')/YEAR/MONTH/DAY/SLUG )
>>> um.add( TYPE('post')/YEAR/MONTH/DAY )
>>> um.add( TYPE('post')/YEAR/MONTH )
>>> um.add( TYPE('post')/YEAR )

Mapping a set of attributes to a path or URL is a matter of specifying the extension of the document (e.g. ‘html’ or ‘css’) and a set of key-value attributes.

>>> um.path('html', type='post', slug='foo', day=15, month=3, year=2010)
'/path/to/output/directory/2010/03/15/foo/index.html'
>>> um.url('html', type='post', slug='foo', day=15, month=3, year=2010)
'http://permanent.url/2010/03/15/foo/'

If the attributes do not correspond to any path definition, then the value None is returned:

>>> um.path('html', non_existent_attribute=True)
>>> um.url('html', non_existent_attribute=True)

If the extension is None, then the path() and url() methods will not add any information to account for an extension.

>>> um.path(None, type='post', slug='foo', day=15, month=3, year=2010)
'/path/to/output/directory/2010/03/15/foo'
>>> um.url(None, type='post', slug='foo', day=15, month=3, year=2010)
'http://permanent.url/2010/03/15/foo'

As a special corner case, the url() method will return the permalink root if it is asked for extension=None with no arguments.

>>> um.url(None)
'http://permanent.url/'

This is useful when it is known that the attributes specified promise to resolve to a path. Example uses include static files that are simply copied into the output directory.

add(path)
Add the path to the list of paths in the URLMapper
path(extension, **kwargs)
Return the filesystem path corresponding to a set of attributes.
url(extension, **kwargs)
Return the URL corresponding to a set of attributes.

Table Of Contents

Previous topic

firmant.parsers.tags

Next topic

firmant.routing.components

This Page