Traversal — flatland 0.8.1 documentation

# 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