I'm building an app that queries some data in an external relational database, using collective.lead (trunk). The user can modify the database connection settings in a custom Plone control panel tool (I followed the example in Aspeli's Professional Plone Development book). The database settings are queried in this way.
My product's base configure.zcml sets up an utility for the database:
<include package="plone.app.registry" />
<include package="collective.lead" />
<i18n:registerTranslations directory="locales" />
<utility
provides="collective.lead.interfaces.IDatabase"
factory=".dbsettings.CalculatorDatabase"
name="test.calc.db"
/>
dbsettings.py has:
from zope.component import getUtility
from plone.registry.interfaces import IRegistry
class CalculatorDatabase(Database):
@property
def _url(self):
registry = getUtility(IRegistry)
settings = registry.forInterface(IDatabaseSettings)
return URL(
drivername=settings.drivername,
username=settings.username,
password=settings.password,
host=settings.hostname,
port=settings.port,
database=settings.database,
)
This raises a ComponentLookupError exception at runtime:
File "/home/zope/envs/test-web/src/test.calc/test/calc/dbsettings.py", line 38, in _url
registry = getUtility(IRegistry)
File "/home/zope/envs/test-web/eggs/zope.component-3.7.1-py2.6.egg/zope/component/_api.py", line 171, in getUtility
raise ComponentLookupError(interface, name)
zope.configuration.config.ConfigurationExecutionError: <class 'zope.component.interfaces.ComponentLookupError'>: (<InterfaceClass plone.registry.interfaces.IRegistry>, '')
in:
File "/home/zope/envs/test-web/src/test.calc/test/calc/configure.zcml", line 26.2-30.6
<utility
provides="collective.lead.interfaces.IDatabase"
factory=".dbsettings.CalculatorDatabase"
name="test.calc.db"
/>
Why isn't the Registry found at runtime? What am I doing wrong?
Thanks.
What Steve mentions is the root of the problem, so I will only add a workaround for the cases where you cannot do exception handling. In a similar scenario I needed to register a utility which depended on settings stored in the registry. The utility could not be registered without these settings, so I absolutely wanted things to happen in order.
The workaround was to not register the utility in zcml, but instead do it inside the following subscriber:
<subscriber
for="Products.CMFPlone.interfaces.IPloneSiteRoot
zope.app.publication.interfaces.IBeforeTraverseEvent"
handler=".component.setupStuff"
/>
This would guarantee that setupStuff
would have access to all the local utilities. Note also that setupStuff
would also return quickly after querying if the utility already exists, as the subscriber will trigger on every request.
collective.lead has been superseded by http://pypi.python.org/pypi/z3c.saconfig which allows you to define database connections in zcml. It can be used with http://pypi.python.org/pypi/collective.saconnect if you need a control panel to configure connection.
The registry is a local component. Each Plone site (and there may be many in a database) has its own. So, it's context dependent.
Zope figures out that context in the course of traversal (the connecting of a URL to an object). That (pretty much) means that you can only look up the registry in the context of a request. So, you can't look up the registry in startup code.
This can lead to chicken-and-egg problems writing code. One solution is to embed the lookup in a try/except that handles the lookup exception gracefully if you don't yet have traversal context.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With