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
