zope.component.adapter.provideAdapter vs. zcml registration

What causes my data converter not to fire unless I register it in zcml?

<!-- Converters -->
<adapter factory=".converters.PhoneNumberDataConverter"/>
# -*- coding: utf-8 -*-
from .browser.widgets import IPhoneNumberWidget
from z3c.form.converter import FieldDataConverter
from z3c.form.interfaces import ITextWidget
from zope.component import adapter
from zope.component import provideAdapter
from zope.schema.interfaces import ITextLine

import logging
import phonenumbers


log = logging.getLogger(__name__)


@adapter(ITextLine, IPhoneNumberWidget)
class PhoneNumberDataConverter(FieldDataConverter):
    """Normalize an already validated phone number"""

    def toWidgetValue(self, value):
        if value:
            value = phonenumbers.parse(value, "DE")
            value = phonenumbers.format_number(value, phonenumbers.PhoneNumberFormat.INTERNATIONAL)

        return value


provideAdapter(PhoneNumberDataConverter)

Am I reading this wrong?

I don’t remember the details, but it sounds like a different component registry is active at import time compared to while ZCML is being loaded.

1 Like

I think maybe there is a more basic question or understanding here.

the Python code in your converter.py will need to run somewhen during startup so that the adapter is being run and the converter registered. You do this by regostering it in zcml.

You could maybe also do this in an init.py (add the __ yourself, forum markup interferes). But with zcml it provably runs at the right place in the startup process. Above my dev skills, bit off guessing here. Who adds? :innocent:

1 Like

Sounds right to me. The normal way of registering is with ZCML. That also provides a better separation between code (to potentially be re-used) and configuration (to potentially be overridden). Doing that in python on load time has side effects and smells plain wrong.

In (doc)tests it’s a good approach, though you can also use loadzcml. That the python API is often used in doctests, where it makes sense, has the unfortunate side effect that the documented way of doing things is not the way you should actually do things, outside of tests.

1 Like

Thank you @davisagli @fredvd and @gyst for the clarifications. Yes, config is obviously the way to go here.

I got tempted to do this in code (different project) while adding literally a dozen or more new content types. Each is based on a separate schema to access its SQLAlchemy representation of a RDBMS table, so there is an adapter for both the container folder and its traject URL, plus any odd converters...

    <adapter
        factory=".adapters.article.ArticleListing"
        for=".schemas.article.IArticle"
        />

    <adapter factory="collective.trajectory.components.Traverser"
             for=".interfaces.IArticleContainer
                  zope.publisher.interfaces.IRequest"/>

You can move the ‘for’ from ZCML into Python, by using the ‘@adapter’ decorator. That simplifies your ZCML. Only hook up the factory in ZCML, have the rest of the adapter configuration in code.

I’m also wondering if you can perhaps have less, but smarter, adapter factories based on shared marker interfaces, where those “smart” factories then delegate to the “actual” adapters in code. I don’t have an example at hand. Probably a bad idea; saving a few lines of config now and having to maintain complex indirections for the rest of the lifetime of your project… :see_no_evil_monkey: