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