================
Using registries
================

You can create a new registry simply by instantiating the Registry class. The
class and its data structures are persistent, so you can store them in the
ZODB. You may want to provide the registry object as local utility for easy
access as well, though we won't do that here.

    >>> from plone.registry import Registry
    >>> registry = Registry()

The registry starts out empty. To access the registry's records, you can use
the `records` property. This exposes a dict API where keys are strings and
values are objects providing `IRecords`.

    >>> len(registry.records)
    0

Simple records
==============

Let's now create a record. A record must have a name. This should be a dotted
name, and contain ASCII characters only. By convention, it should be all
lowercase and start with the name of the package that defines the record.

It is also possible to create a  number of records based on a single schema
interface - see below - but for now, we will focus on simple records.

Before we can create the record, we must create the field that describes it.
Fields are based on the venerable zope.schema package, but plone.registry
only supports certain fields, and disallows use of a few properties even
of those. As a rule of thumb, so long as a field stores a Python primitive,
it is supported; the same goes for attributes of fields.

Thus:

 * Fields like `Object`, `InterfaceField` and so on are not supported
 * A custom `constraint` method is not supported.
 * The `order` attribute will always be set to -1
 * For Choice fields, only named vocabularies are supported: you cannot
   reference a particular source or source binder
 * The key_type and value_type properties of Dict, List, Tuple, Set and
   Frozenset may only contain persistent fields.

See field.text for more details.

Creating a record
-----------------

The supported field types are found in the module plone.registry.field. These
are named the same as the equivalent field in zope.schema, and have the same
constructors. You must use one of these fields when creating records directly.

    >>> from plone.registry import field
    >>> age_field = field.Int(title=u"Age", min=0, default=18)

    >>> from plone.registry import Record
    >>> age_record = Record(age_field)

Note that in this case, we did not supply a value. The value will therefore
be the field default.

    >>> age_record.value
    18

We can set a different value, either in the `Record` constructor or via the
`value` attribute:

    >>> age_record.value = 2
    >>> age_record.value
    2

Note that the value is validated against the field:

    >>> age_record.value = -1
    Traceback (most recent call last):
    ...
    TooSmall: (-1, 0)

    >>> age_record.value
    2

We can now add the field to the registry. This is done via the `record`
dictionary.

    >>> 'plone.registry.tests.age' in registry
    False
    >>> registry.records['plone.registry.tests.age'] = age_record

At this point, the record will gain __name__ and __parent__ attributes.

    >>> age_record.__name__
    'plone.registry.tests.age'

    >>> age_record.__parent__ is registry
    True

Creating a record with an initial value
---------------------------------------

We can create records more succinctly by creating the field, setting the value
and assigning it to the registry in one go, like this:

    >>> registry.records['plone.registry.tests.cms'] = \
    ...     Record(field.TextLine(title=u"CMS of choice"), u"Plone")

The record can now be obtained. Note that it has a nice __repr__ to help
debugging.

    >>> registry.records['plone.registry.tests.cms']
    <Record plone.registry.tests.cms>

Accessing and manipulating record values
----------------------------------------

Once a record has been created and added to the registry, you can access
its value through dict-like operations on the registry itself.

    >>> 'plone.registry.tests.cms' in registry
    True

    >>> registry['plone.registry.tests.cms']
    u'Plone'

    >>> registry['plone.registry.tests.cms'] = u"Plone 3.x"

Again, values are validated:

    >>> registry['plone.registry.tests.cms'] = 'Joomla'
    Traceback (most recent call last):
    ...
    WrongType: ('Joomla', <type 'unicode'>...)

There is also a get() method:

    >>> registry.get('plone.registry.tests.cms')
    u'Plone 3.x'
    >>> registry.get('non-existent-key') is None
    True

Deleting records
----------------

Records may be deleted from the `records` property:

    >>> del registry.records['plone.registry.tests.cms']
    >>> 'plone.registry.tests.cms' in registry.records
    False
    >>> 'plone.registry.tests.cms' in registry
    False

Creating records from interfaces
================================

As an application developer, it is often desirable to define settings as
traditional interfaces with zope.schema fields. plone.registry includes
support for creating a set of records from a single interface.

To test this, we have created an interface, `IMailSettings`, with two fields,
`sender` and `smtp_host`.

    >>> from plone.registry.tests import IMailSettings

Note that this contains standard fields.

    >>> IMailSettings['sender']
    <zope.schema._bootstrapfields.TextLine object at ...>

    >>> IMailSettings['smtp_host']
    <zope.schema._field.URI object at ...>

We can create records from this interface like this:

    >>> registry.registerInterface(IMailSettings)

One record for each field in the interface has now been created. Their names
are the full dotted names to those fields:

    >>> sender_record = registry.records['plone.registry.tests.IMailSettings.sender']
    >>> smtp_host_record = registry.records['plone.registry.tests.IMailSettings.smtp_host']

The fields used in the records will be the equivalent persistent versions of
the fields from the original interface.

    >>> sender_record.field
    <plone.registry.field.TextLine object at ...>

    >>> smtp_host_record.field
    <plone.registry.field.URI object at ...>

This feat is accomplished internally by adapting the field to the
IPersistentField interface. There is a default adapter factory that works for
all fields defined in plone.registry.field. You can of course define your own
adapter if you have a custom field type, but bear in mind the golden rules of
any persistent field:

  * The field must store only primitives or other persistent fields
  * It must not reference a function, class, interface or other method that
    could break if a package is uninstalled.

If we have a field for which there is no IPersistentField adapter, we will
get an error:

    >>> from plone.registry.tests import IMailPreferences
    >>> IMailPreferences['settings']
    <zope.schema._field.Object object at ...>

    >>> registry.registerInterface(IMailPreferences)
    Traceback (most recent call last):
    ...
    TypeError: There is no persistent field equivalent for the field `settings` of type `Object`.

Whoops! We can, however, tell registerInterface() to ignore one or more
fields.

    >>> registry.registerInterface(IMailPreferences, omit=('settings',))

Once an interface's records have been registered, we can get and set their
values as normal:

    >>> registry['plone.registry.tests.IMailSettings.sender']
    u'root@localhost'

    >>> registry['plone.registry.tests.IMailSettings.sender'] = u"webmaster@localhost"
    >>> registry['plone.registry.tests.IMailSettings.sender']
    u'webmaster@localhost'

If we sub-sequently re-register the same interface, the value will be retained
if possible:

    >>> registry.registerInterface(IMailSettings)
    >>> registry['plone.registry.tests.IMailSettings.sender']
    u'webmaster@localhost'

However, if the value is no longer valid, we will revert to the default. To
test that, let's sneakily modify the field for a while.

    >>> old_field = IMailSettings['sender']
    >>> IMailSettings._InterfaceClass__attrs['sender'] = field.Int(title=u"Definitely not a string", default=2)
    >>> if hasattr(IMailSettings, '_v_attrs'):
    ...     del IMailSettings._v_attrs['sender']
    >>> registry.registerInterface(IMailSettings)
    >>> registry['plone.registry.tests.IMailSettings.sender']
    2

But let's put it back the way it was.

    >>> IMailSettings._InterfaceClass__attrs['sender'] = old_field
    >>> if hasattr(IMailSettings, '_v_attrs'):
    ...     del IMailSettings._v_attrs['sender']
    >>> registry.registerInterface(IMailSettings)
    >>> registry['plone.registry.tests.IMailSettings.sender']
    u'root@localhost'

Sometimes, you may want to use an interface as a template for multiple
instances of a set of fields, rather than defining them all by hand. This is
especially useful when you want to allow third-party packages to provide
information. To accomplish this, we can provide a prefix with the
`registerInterface` call. This will take precedence over the __identifier__
that is usually used.

    >>> registry.registerInterface(IMailSettings, prefix="plone.registry.tests.alternativesettings")

These values are now available in the same way as the original settings:

    >>> sender_record = registry.records['plone.registry.tests.alternativesettings.sender']
    >>> smtp_host_record = registry.records['plone.registry.tests.alternativesettings.smtp_host']
    >>> registry['plone.registry.tests.alternativesettings.sender'] = u'alt@example.org'

Accessing the original interface
--------------------------------

Now that we have these records, we can look up the original interface. This
does not break the golden rules: internally, we only store the name of the
interface, and resolve it at runtime.

Records that know about interfaces are marked with `IInterfaceAwareRecord` and
have two additional properties: `interface` and `fieldName`.

    >>> from plone.registry.interfaces import IInterfaceAwareRecord
    >>> IInterfaceAwareRecord.providedBy(age_record)
    False
    >>> IInterfaceAwareRecord.providedBy(sender_record)
    True

    >>> sender_record.interfaceName
    'plone.registry.tests.IMailSettings'

    >>> sender_record.interface is IMailSettings
    True

Using the records proxy
-----------------------

Once the records for an interface has been created, it is possible to obtain
a proxy object that provides the given interface, but reads and writes its
values to the registry. This is useful, for example, to create a form using
`zope.formlib` or `z3c.form` that is configured with widgets based on the
interface, or simply as a more convenient API when working with multiple,
related settings.

    >>> proxy = registry.forInterface(IMailSettings)
    >>> proxy
    <RecordsProxy for plone.registry.tests.IMailSettings>

If you use your registry values in code
which might be encountered on normal HTML rendering
paths (e.g. in a viewlet) you need to be aware that records
might not exist or they are invalid. `forInterface()`
will raise KeyError on this kind of situations::

   try:
       proxy = registry.forInterface(IMailSettings)
   except KeyError:
       # Gracefully handled cases
       # when GenericSetup installer has not been run or rerun
       # e.g. by returning or using some default values
       pass

The proxy is not a persistent object on its own.

    >>> from persistent.interfaces import IPersistent
    >>> IPersistent.providedBy(proxy)
    False

It does, however, provide the requisite interface.

    >>> IMailSettings.providedBy(proxy)
    True

You can distinguish between the proxy and a 'norma' object by checking for the
IRecordsProxy marker interface:

    >>> from plone.registry.interfaces import IRecordsProxy
    >>> IRecordsProxy.providedBy(proxy)
    True

When we set a value, it is stored in the registry:

    >>> proxy.smtp_host = 'http://mail.server.com'
    >>> registry['plone.registry.tests.IMailSettings.smtp_host']
    'http://mail.server.com'

    >>> registry['plone.registry.tests.IMailSettings.smtp_host'] = 'smtp://mail.server.com'
    >>> proxy.smtp_host
    'smtp://mail.server.com'

Values not in the interface will raise an AttributeError:

    >>> proxy.age
    Traceback (most recent call last):
    ...
    AttributeError: age

Note that by default, the forInterface() method will check that the necessary
records have been registered. For example, we cannot use any old interface:

    >>> registry.forInterface(IInterfaceAwareRecord)
    Traceback (most recent call last):
    ...
    KeyError: 'Interface `plone.registry.interfaces.IInterfaceAwareRecord` defines a field `interface`, for which there is no record.'

By default, we also cannot use an interface for which only some records exist:

    >>> registry.forInterface(IMailPreferences)
    Traceback (most recent call last):
    ...
    KeyError: 'Interface `plone.registry.tests.IMailPreferences` defines a field `settings`, for which there is no record.'

It is possible to disable this check, however. This will be a bit more
efficient:

    >>> registry.forInterface(IMailPreferences, check=False)
    <RecordsProxy for plone.registry.tests.IMailPreferences>

A better way, however, is to explicitly declare that some fields are omitted:

    >>> pref_proxy = registry.forInterface(IMailPreferences, omit=('settings',))

In this case, the omitted fields will default to their 'missing' value:

    >>> pref_proxy.settings ==  IMailPreferences['settings'].missing_value
    True

However, trying to set the value will result in a AttributeError:

    >>> pref_proxy.settings = None
    Traceback (most recent call last):
    ...
    AttributeError: settings

To access another instance of the field, supply the prefix:

    >>> alt_proxy = registry.forInterface(IMailSettings,
    ...     prefix="plone.registry.tests.alternativesettings")
    >>> alt_proxy.sender
    u'alt@example.org'

Collections of records proxies
------------------------------

A collection of record sets may be accessed using `collectionOfInterface`:

    >>> collection = registry.collectionOfInterface(IMailSettings)

You can create a new record set:

    >>> proxy = collection.setdefault('example')
    >>> proxy.sender = u'collection@example.org'
    >>> proxy.smtp_host = 'smtp://mail.example.org'

Record sets are stored based under the prefix:

    >>> prefix = IMailSettings.__identifier__
    >>> registry.records.values(prefix+'/', prefix+'0')
    [<Record plone.registry.tests.IMailSettings/example.sender>,
     <Record plone.registry.tests.IMailSettings/example.smtp_host>]
    >>> registry['plone.registry.tests.IMailSettings/example.sender']
    u'collection@example.org'

Records may be set from an existing object:

    >>> class MailSettings:
    ...     sender = u'someone@example.com'
    ...     smtp_host = 'smtp://mail.example.com'
    >>> collection['example_com'] = MailSettings()
    >>> registry.records.values(prefix+'/', prefix+'0')
    [<Record plone.registry.tests.IMailSettings/example.sender>,
     <Record plone.registry.tests.IMailSettings/example.smtp_host>,
     <Record plone.registry.tests.IMailSettings/example_com.sender>,
     <Record plone.registry.tests.IMailSettings/example_com.smtp_host>]

The collection may be iterated over:

    >>> for name in collection: print name
    example
    example_com

And may be deleted:

    >>> del collection['example_com']
    >>> registry.records.values(prefix+'/', prefix+'0')
    [<Record plone.registry.tests.IMailSettings/example.sender>,
     <Record plone.registry.tests.IMailSettings/example.smtp_host>]

Using field references
======================

It is possible for one record to refer to another record's field. This can be
used to provide a simple "override" mechanism, for example, where one record
defines the field and a default value, whilst another provides an override
validated against the same field.

Let us first create the base record and set its value:

    >>> timeout_field = field.Int(title=u"Timeout", min=0)
    >>> registry.records['plone.registry.tests.timeout'] = Record(timeout_field, 10)

    >>> timeout_record = registry.records['plone.registry.tests.timeout']
    >>> timeout_record.value
    10

Next, we create a field reference for this record:

    >>> from plone.registry import FieldRef
    >>> timeout_override_field = FieldRef(timeout_record.__name__, timeout_record.field)

We can use this to create a new record:

    >>> registry.records['plone.registry.tests.timeout.override'] = Record(timeout_override_field, 20)
    >>> timeout_override_record = registry.records['plone.registry.tests.timeout.override']

The two values are separate:

    >>> timeout_record.value
    10
    >>> timeout_override_record.value
    20

    >>> registry['plone.registry.tests.timeout']
    10
    >>> registry['plone.registry.tests.timeout.override']
    20

Validation uses the underlying field:

    >>> registry['plone.registry.tests.timeout.override'] = -1
    Traceback (most recent call last):
    ...
    TooSmall: (-1, 0)

The reference field exposes the standard field properties, e.g.:

    >>> timeout_override_record.field.title
    u'Timeout'
    >>> timeout_override_record.field.min
    0

To look up the underlying record name, we can use the ``recordName`` property:

    >>> timeout_override_record.field.recordName
    'plone.registry.tests.timeout'

