From: Laurence R. <svn...@pl...> - 2011-03-31 12:39:46
|
Author: ldr Date: Thu Mar 31 12:38:15 2011 New Revision: 48300 Modified: plone.registry/trunk/ (props changed) plone.registry/trunk/docs/HISTORY.txt plone.registry/trunk/plone/registry/interfaces.py plone.registry/trunk/plone/registry/recordsproxy.py plone.registry/trunk/plone/registry/registry.py plone.registry/trunk/plone/registry/registry.txt Log: Add collectionOfInterface support to registry. Modified: plone.registry/trunk/docs/HISTORY.txt ============================================================================== --- plone.registry/trunk/docs/HISTORY.txt (original) +++ plone.registry/trunk/docs/HISTORY.txt Thu Mar 31 12:38:15 2011 @@ -4,6 +4,9 @@ 1.0b5 - unreleased ------------------ +* Add ``collectionOfInterface`` support to registry. + [elro] + * Fixed bug where prefix was ignored by registry.forInterface. [elro] Modified: plone.registry/trunk/plone/registry/interfaces.py ============================================================================== --- plone.registry/trunk/plone/registry/interfaces.py (original) +++ plone.registry/trunk/plone/registry/interfaces.py Thu Mar 31 12:38:15 2011 @@ -4,6 +4,11 @@ from zope import schema from zope.schema.interfaces import IField +from zope.schema.interfaces import InvalidDottedName + +class InvalidRegistryKey(InvalidDottedName): + """A registry key is a dotted name with up to one '/'. + """ class IPersistentField(IField): """A field that can be persistent along with a record. Modified: plone.registry/trunk/plone/registry/recordsproxy.py ============================================================================== --- plone.registry/trunk/plone/registry/recordsproxy.py (original) +++ plone.registry/trunk/plone/registry/recordsproxy.py Thu Mar 31 12:38:15 2011 @@ -1,6 +1,11 @@ from zope.interface import implements, alsoProvides +from zope.schema import getFieldsInOrder +from zope.schema.interfaces import RequiredMissing from plone.registry.interfaces import IRecordsProxy +from UserDict import DictMixin +import re + _marker = object() class RecordsProxy(object): @@ -11,9 +16,8 @@ def __init__(self, registry, schema, omitted=(), prefix=None): if prefix is None: - prefix = schema.__identifier__ - - if not prefix.endswith("."): + prefix = schema.__identifier__ + '.' + elif not prefix.endswith("."): prefix += '.' # skip __setattr__ @@ -43,3 +47,96 @@ def __repr__(self): return "<RecordsProxy for %s>" % self.__schema__.__identifier__ + + +class RecordsProxyCollection(DictMixin): + """A proxy that maps a collection of RecordsProxy objects + """ + + _validkey = re.compile(r"([a-zA-Z][a-zA-Z0-9_]*)$").match + + # ord('.') == ord('/') - 1 + + def __init__(self, registry, schema, check=True, omitted=(), prefix=None): + if prefix is None: + prefix = schema.__identifier__ + + if not prefix.endswith("/"): + prefix += '/' + + self.registry = registry + self.schema = schema + self.check = check + self.omitted = omitted + self.prefix = prefix + + def __getitem__(self, key): + if self.has_key(key): + prefix = self.prefix + key + proxy = self.registry.forInterface(self.schema, self.check, self.omitted, prefix) + return proxy + raise KeyError(key) + + def __iter__(self): + min = self.prefix + max = self.prefix[:-1] + '0' + keys = self.registry.records.keys(min, max) + len_prefix = len(self._prefix) + last = None + for name in keys: + name = name[len_prefix:] + key, extra = name.split('/', 1) + if key != last: + yield key + last = key + + def _validate(self, key): + if not isinstance(key, basestring) or not self._validkey(key): + raise TypeError('expected a valid key (alphanumeric or underscore, starting with alpha)') + return str(key) + + def has_key(self, key): + key = self._validate(key) + prefix = self.prefix + key + names = self.registry.records.keys(prefix+'.', prefix+'/') + return bool(names) + + def add(self, key): + key = self._validate(key) + prefix = self.prefix + key + self.registry.registerInterface(self.schema, self.omitted, prefix) + proxy = self.registry.forInterface(self.schema, False, self.omitted, prefix) + return proxy + + def __setitem__(self, key, value): + key = self._validate(key) + data = {} + for name, field in getFieldsInOrder(self.schema): + if name in self.omitted or field.readonly: + continue + attr = getattr(value, name, _marker) + if attr is not _marker: + data[name] = attr + elif field.required and self.check: + raise RequiredMissing(name) + + proxy = self.add(key) + for name, attr in data.items(): + setattr(proxy, name, attr) + + def setdefault(self, key, failobj=None): + if not self.has_key(key): + if failobj is None: + self.add(key) + else: + self[key] = failobj + return self[key] + + def __delitem__(self, key): + if not self.has_key(key): + raise KeyError(key) + prefix = self.prefix + key + names = list(self.registry.records.keys(prefix+'.', prefix+'/')) + for name in names: + del self.registry.records[name] + \ No newline at end of file Modified: plone.registry/trunk/plone/registry/registry.py ============================================================================== --- plone.registry/trunk/plone/registry/registry.py (original) +++ plone.registry/trunk/plone/registry/registry.py Thu Mar 31 12:38:15 2011 @@ -1,3 +1,4 @@ +import re import warnings from persistent import Persistent @@ -8,14 +9,16 @@ from zope.event import notify from zope.schema import getFieldNames, getFieldsInOrder -from zope.schema.interfaces import InvalidDottedName + from zope.schema._field import _isdotted from plone.registry.interfaces import IRegistry, IRecord, IPersistentField from plone.registry.interfaces import IFieldRef +from plone.registry.interfaces import InvalidRegistryKey from plone.registry.record import Record from plone.registry.fieldref import FieldRef from plone.registry.recordsproxy import RecordsProxy +from plone.registry.recordsproxy import RecordsProxyCollection from plone.registry.events import RecordAddedEvent, RecordRemovedEvent class Registry(Persistent): @@ -104,7 +107,10 @@ value = persistent_field.default self.records[record_name] = Record(persistent_field, value, _validate=False) - + + def collectionOfInterface(self, interface, check=True, omit=(), prefix=None): + return RecordsProxyCollection(self, interface, check, omit, prefix) + # BBB def _migrateRecords(self): @@ -131,6 +137,15 @@ __parent__ = None + # Similar to zope.schema._field._isdotted, but allows up to one '/' + _validkey = re.compile( + r"([a-zA-Z][a-zA-Z0-9_]*)" + r"([.][a-zA-Z][a-zA-Z0-9_]*)*" + r"([/][a-zA-Z][a-zA-Z0-9_]*)?" + r"([.][a-zA-Z][a-zA-Z0-9_]*)*" + # use the whole line + r"$").match + def __init__(self, parent): self.__parent__ = parent @@ -138,8 +153,8 @@ self._values = OOBTree() def __setitem__(self, name, record): - if not _isdotted(name): - raise InvalidDottedName(record) + if not self._validkey(name): + raise InvalidRegistryKey(record) if not IRecord.providedBy(record): raise ValueError("Value must be a record") Modified: plone.registry/trunk/plone/registry/registry.txt ============================================================================== --- plone.registry/trunk/plone/registry/registry.txt (original) +++ plone.registry/trunk/plone/registry/registry.txt Thu Mar 31 12:38:15 2011 @@ -397,6 +397,47 @@ >>> alt_proxy.sender u'al...@ex...' +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'col...@ex...' + >>> 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'col...@ex...' + +Records may be set from an existing object: + + >>> class MailSettings: + ... sender = u'so...@ex...' + ... 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>] + +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 ====================== |