Annotations & Properties — flatland 0.8.1 documentation

# Annotations & Properties

Flatland provides two options for annotating schemas and data.

## Standard Python

Element schemas are normal Python classes and can be extended in all of the usual ways. For example, you can add an attribute when subclassing:

    from flatland import String

    class Textbox(String):
        tooltip = 'Undefined'

Once an attribute has been added to an element class, its value can be overridden by further subclassing or, more compactly, with the `using()` schema constructor:

    class Password(Textbox):
        tooltip = 'Enter your password'

    Password = Textbox.using(tooltip='Enter your password')
    assert Password.tooltip == 'Enter your password'

Both are equivalent, and the custom `tooltip` will be inherited by any subclasses of `Password`. Likewise, instances of `Password` will have the attribute as well.

    el = Password()
    assert el.tooltip == 'Enter your password'

And because the `Element()` constructor allows overriding any schema attribute by keyword argument, individual element instances can be constructed with own values, masking the value provided by their class.

    password_match = Textbox(tooltip='Enter your password again')

    assert password_match.tooltip == 'Enter your password again'

## Properties

Another option for annotation is the `properties` mapping of element classes and instances. Unlike class attributes, almost any object you like can be used as the key in the mapping.

The unique feature of `properties` is data inheritance:

    from flatland import String

    # Textboxes are Strings with tooltips
    Textbox = String.with_properties(tooltip='Undefined')

    # A Password is a Textbox with a custom tooltip message
    Password = Textbox.with_properties(tooltip='Enter your password')

    assert Textbox.properties['tooltip'] == 'Undefined'
    assert Password.properties['tooltip'] == 'Enter your password'

Annotations made on a schema are visible to itself and any subclasses, but not to its parents.

    # Add disabled to all Textboxes
    Textbox.properties['disabled'] = False

    # disabled is inherited from Textbox
    assert Password.properties['disabled'] is False

    # changes in a subclass do not affect the parent
    del Password.properties['disabled']
    assert 'disabled' in Textbox.properties

## Annotating With Properties

To create a new schema that includes additional properties, construct it with `with_properties()`:

    Textbox = String.with_properties(tooltip='Undefined')

Or if the schema has already been created, manipulate its `properties` mapping:

    class Textbox(String):
       pass

    Textbox.properties['tooltip'] = 'Undefined'

The `properties` mapping is implemented as a view over the Element schema inheritance hierarchy. If annotations are added to a superclass such as `String`, they are visible immediately to all Strings and subclasses.

## Private Annotations

To create a schema with completely unrelated properties, not inheriting from its superclass at all, declare it with `using()`:

    Alone = Textbox.using(properties={'something': 'else'})
    assert 'tooltip' not in Alone.properties

Or when subclassing longhand, construct a `Properties` collection explicitly.

    from flatland import Properties

    class Alone(Textbox):
       properties = Properties(something='else')

    assert 'tooltip' not in Alone.properties

An instance may also have a private collection of properties. This can be done either at or after construction:

    solo1 = Textbox(properties={'something': 'else'})

    solo2 = Textbox()
    solo2.properties = {'something': 'else'}

    Textbox.properties['background_color'] = 'red'

    assert 'background_color' not in solo1.properties
    assert 'background_color' not in solo2.properties