Flatland provides two options for annotating schemas and data.
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'
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
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.
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