Traversal¶
Flatland supplies a rich set of tools for working with structured data. For this section, we’ll use the following schema as an example. It is simple yet has a bit of variety in its structure.
from flatland import Form, Dict, List, String, Integer
class Annotation(Form):
"""A spot on a 2D surface."""
title = String
flags = List.of(Integer)
location = Dict.of(Integer.named('x'),
Integer.named('y'))
sample_data = {
'title': 'Interesting Spot',
'flags': [1, 3, 5],
'location': {'x': 10, 'y': 20},
}
ann1 = Annotation(sample_data, name=u'ann1')
Going Raw¶
You may not even need to use any of these traversal strategies in your
application. An element’s value is a full & recursive
“export” of its native Python value. Many times this is sufficient.
>>> ann1['title'] # ann1 is a flatland structure
<String u'title'; value=u'Interesting Spot'>
>>> type(ann1.value) # but its .value is not
<type 'dict'>
>>> ann1.value == sample_data
True
Python Syntax¶
Containers elements such as Form, Dict,
and List implement the Python methods you’d expect for
their type. In most cases you may use them as if they were dict and
list instances- the difference being that they always contain
Element instances.
For example, Form and Dict can be indexed and used like dict:
>>> ann1['title'].value
u'Interesting Spot'
>>> ann1['location']['x'].value
10
>>> sorted(ann1['location'].items())
[(u'x', <Integer u'x'; value=10>), (u'y', <Integer u'y'; value=20>)]
>>> u'title' in ann1
True
And List and similar types can be used like lists:
>>> ann1['flags']
[<Integer None; value=1>, <Integer None; value=3>, <Integer None; value=5>]
>>> ann1['flags'][0].value
1
>>> ann1['flags'].value
[1, 3, 5]
>>> Integer(3) in ann1['flags']
True
>>> 3 in ann1['flags']
True
The final example is of special note: the value in the expression is not an
Element. Most containers will accept native Python values in these types
of expressions and convert them into a temporary Element for the
operation. The example below is equivalent to the example above.
>>> ann1['flags'].member_schema(3) in ann1['flags']
True
Traversal Properties¶
Elements of all types support a core set of properties that allow navigation
to related elements: root,
parents, children, and
all_children.
>>> list(ann1['flags'].children)
[<Integer None; value=1>, <Integer None; value=3>, <Integer None; value=5>]
>>> list(ann1['title'].children) # title is a String and has no children
[]
>>> sorted(el.name for el in ann1.all_children if el.name)
[u'flags', u'location', u'title', u'x', u'y']
>>> [el.name for el in ann1['location']['x'].parents]
[u'location', u'ann1']
Each of these properties (excepting root) returns an iterator of
elements.
Path Lookups¶
Another option for operating on elements is the find()
method. find selects elements by path, a string that represents one or
more related elements. Looking up elements by path is a powerful technique to
use when authoring flexible & reusable validators.
>>> ann1.find('title') # find 'ann1's child named 'title'
[<String u'title'; value=u'Interesting Spot'>]
Paths are evaluated relative to the element:
>>> ann1['location'].find('x')
[<Integer u'x'; value=10>]
Referencing parents is possible with ..:
>>> ann1['location']['x'].find('../../title')
[<String u'title'; value=u'Interesting Spot'>]
Absolute paths begin with a /.
>>> ann1['location']['x'].find('/title')
[<String u'title'; value=u'Interesting Spot'>]
Members of sequences can be selected like any other child (their index number is their name), or you can use Python-like slicing:
>>> ann1.find('/flags/0')
[<Integer None; value=1>]
>>> ann1.find('/flags[0]')
[<Integer None; value=1>]
Full Python slice notation is supported as well. With slices, paths can select more than one element.
>>> ann1.find('/flags[:]')
[<Integer None; value=1>, <Integer None; value=3>, <Integer None; value=5>]
>>> ann1.find('/flags[1:]')
[<Integer None; value=3>, <Integer None; value=5>]
Further path operations are permissible after slices. A richer schema is needed to illustrate this:
>>> Points = List.of(List.of(Dict.of(Integer.named('x'),
... Integer.named('y'))))
>>> p = Points([[dict(x=1, y=1), dict(x=2, y=2)],
... [dict(x=3, y=3)]])
>>> p.find('[:][:]/x')
[<Integer u'x'; value=1>, <Integer u'x'; value=2>, <Integer u'x'; value=3>]
The equivalent straight Python to select the same set of elements is quite a bit more wordy.
Path Syntax¶
/(leading slash)- Selects the root of the element tree
element- The name of a child element
element/child/separates path segments..- Traverse to the parent element
element[0]- For a sequence container element, select the 0th child
element[:]- Select all children of a container element (need not be a sequence)
element[1:5]- Select a slice of a sequence container’s children