Non-ZCML config for ZCA. (Was: Anyone want to do Google Summer of code mentoring for PSF?)
I haven't read through the whole discussion in detail, so I'm sure I repeat some of what has been said already, but here is my point of view. 1. Yes, Grok is pretty implicit. If it's soo implecit is a matter of taste, but much of the implicitness makes sense. You typically do have a model and a view and a template, and the model and view are typically in one module, and has a name similar to the template. That implicitness however only makes sense in the specific context of web applications. There is no reasonably way to have that implicitness with components and adapters. So a configuration for the ZCA in general can't be implicit. 2. That doesn't mean we can't use grok-style configuration though. 3. Although Python 3 means we can't. We'll have to use decorators. 4. zope.interface already does, and zope.component will as well, once it's ported. That means we get things like: class IMyFace(Interface): whatevah class IMyFeet(Interface): something @implementer(IMyFace) class MyFace(object): yeahyeah @adapter(IMyFace, IMyFeet) class FootInMouth(object): def __init__(self, mouth, foot): pass The @adapter decorator so far only handles functions, but will be extended to classes once we port zope.component. I think also adapter today will only mark the object as adapts(...) does, but you will still use zcml to register the adapter. So probably we still need @adapter (as it already exists) and also another decorator (say @adapt() maybe?) that will both mark the class and register it in the registry, and hence do the same as the <adapter ... /> directive does today. Then we have subscriber, utility, resource and view directives. There's no particular reason I know of that means they couldn't be class decorators as well. That takes care of most of the configuration needed for the ZCA itself. How to deal with the rest probably gets more obvious once we've done this. //Lennart
Also, the decorators will always return the original component, meaning they can easily be used as post-config: @adapter(IMyFace, IMyFeet) class FootInMouth(object): ... Will mark the class as an adapter, but not register it. @adapt(IMyFace, IMyFeet) class FootInMouth(object): ... Will mark and register. @adapter(IMyFace, IMyFeet) class FootInMouht(object): ... adapt(FootInMouth)() Will register a previously marked adapter, and adapt(FootInMouth)(IMyFace, IMyFeet) Will mark and register. This means you can, if you want, still have the interfaces in one file, the implementations in one file, and the registrations separately (say, configure.py), thereby getting the same separation as you had with interfaces.py, components.py and configure.zcml. Your package just needs to *not* import the configure.py file. On Mon, Mar 21, 2011 at 12:47, Lennart Regebro <regebro@gmail.com> wrote:
I haven't read through the whole discussion in detail, so I'm sure I repeat some of what has been said already, but here is my point of view.
1. Yes, Grok is pretty implicit. If it's soo implecit is a matter of taste, but much of the implicitness makes sense. You typically do have a model and a view and a template, and the model and view are typically in one module, and has a name similar to the template. That implicitness however only makes sense in the specific context of web applications. There is no reasonably way to have that implicitness with components and adapters. So a configuration for the ZCA in general can't be implicit.
2. That doesn't mean we can't use grok-style configuration though.
3. Although Python 3 means we can't. We'll have to use decorators.
4. zope.interface already does, and zope.component will as well, once it's ported. That means we get things like:
class IMyFace(Interface): whatevah
class IMyFeet(Interface): something
@implementer(IMyFace) class MyFace(object): yeahyeah
@adapter(IMyFace, IMyFeet) class FootInMouth(object): def __init__(self, mouth, foot): pass
The @adapter decorator so far only handles functions, but will be extended to classes once we port zope.component. I think also adapter today will only mark the object as adapts(...) does, but you will still use zcml to register the adapter. So probably we still need @adapter (as it already exists) and also another decorator (say @adapt() maybe?) that will both mark the class and register it in the registry, and hence do the same as the <adapter ... /> directive does today.
Then we have subscriber, utility, resource and view directives. There's no particular reason I know of that means they couldn't be class decorators as well.
That takes care of most of the configuration needed for the ZCA itself. How to deal with the rest probably gets more obvious once we've done this.
//Lennart _______________________________________________ Zope-Dev maillist - Zope-Dev@zope.org https://mail.zope.org/mailman/listinfo/zope-dev ** No cross posts or HTML encoding! ** (Related lists - https://mail.zope.org/mailman/listinfo/zope-announce https://mail.zope.org/mailman/listinfo/zope )
On Mon, Mar 21, 2011 at 7:47 AM, Lennart Regebro <regebro@gmail.com> wrote: ...
4. zope.interface already does, and zope.component will as well, once it's ported. That means we get things like:
class IMyFace(Interface): whatevah
class IMyFeet(Interface): something
@implementer(IMyFace) class MyFace(object): yeahyeah
@adapter(IMyFace, IMyFeet) class FootInMouth(object): def __init__(self, mouth, foot): pass
The @adapter decorator so far only handles functions, but will be extended to classes once we port zope.component. I think also adapter today will only mark the object as adapts(...) does, but you will still use zcml to register the adapter. So probably we still need @adapter (as it already exists) and also another decorator (say @adapt() maybe?) that will both mark the class and register it in the registry, and hence do the same as the <adapter ... /> directive does today.
Then we have subscriber, utility, resource and view directives. There's no particular reason I know of that means they couldn't be class decorators as well.
That takes care of most of the configuration needed for the ZCA itself. How to deal with the rest probably gets more obvious once we've done this.
I'm not going to comment any more on the broader thread unless/until I have something specific to propose. Having said that, I'd like to go on record as wanting to review the zca port to Python 3 before it's finalized. Jim -- Jim Fulton http://www.linkedin.com/in/jimfulton
Hi there, For Martian, Python 3 support is mostly an issue of making class directives work as class decorators. I'm not sure what Lennart means by point 1. Anyway, Grok's configuration is dependent on the rules you give it, so it's possible to have a completely explicit set of directives with no implicit fallback to default values whatsoever. Martian supports that scenario right now. The only implicit behavior left would be the grokking process, which generally recognizes subclasses of particular base classes as something to register. You could do away with this using class decorators as well, but that would require some changes to Martian to allow it to recognize things that way. (and it won't work for grokking instances, just classes and functions) Note that Pyramid as far as I understand also has a non-ZCML configuration method that is closer to ZCML. Regards, Martijn
On Mon, Mar 21, 2011 at 14:17, Martijn Faassen <faassen@startifact.com> wrote:
Anyway, Grok's configuration is dependent on the rules you give it, so it's possible to have a completely explicit set of directives with no implicit fallback to default values whatsoever. Martian supports that scenario right now.
Sure, but I'm of course referring to how it behaves by default, including the associations made by the grokking process. I think that makes sense in a webapp, but not otherwise, and we should at least as a start concentrate on the generic component architecture case.
For Martian, Python 3 support is mostly an issue of making class directives work as class decorators.
And the same goes for zope.component support, of course. With martian, the registration is then done by the grokking process, but I think decorators would be a process that is more acceptable to the Python world in general. Instances does indeed require something else than decorators, I hadn't thought of that, that's a drawback. //Lennart
On Mon, Mar 21, 2011 at 10:07 AM, Lennart Regebro <regebro@gmail.com> wrote:
On Mon, Mar 21, 2011 at 14:17, Martijn Faassen <faassen@startifact.com> wrote: ... With martian, the registration is then done by the grokking process, but I think decorators would be a process that is more acceptable to the Python world in general. Instances does indeed require something else than decorators, I hadn't thought of that, that's a drawback.
I think Martijn raised a good question about the conceptual interaction of class decorators and inheritance. (Arguablly the questions applies to the "advice"-based syntax as well.) If I see: @some_decorator class Base: .... class Sub(Base): ... I'm going to wonder how the decorator affects Sub. (Wondering is work. :) This might be OK for @implements and maybe @adapts, which describe behavior, but start feeling wonky to me for something like: @utility. Maybe it's enough to document what the directives do. Or maybe something less attached to the class definition would make sense. I don't know what the right answer is ... at least not yet. :) Jim -- Jim Fulton http://www.linkedin.com/in/jimfulton
On 03/21/2011 03:28 PM, Jim Fulton wrote:
I don't know what the right answer is ... at least not yet. :)
In Django and sqlalchemy declarative a meta class is used for this kind of configuration. Since that is subclassing, it implies inheritance, I think. Of course metaclasses are not very good for this as they're surprising in other ways and use unstructured configuration happening at import time. Regards, Martijn
On Mon, Mar 21, 2011 at 10:38 AM, Martijn Faassen <faassen@startifact.com> wrote:
On 03/21/2011 03:28 PM, Jim Fulton wrote:
I don't know what the right answer is ... at least not yet. :)
In Django and sqlalchemy declarative a meta class is used for this kind of configuration. Since that is subclassing, it implies inheritance, I think.
Ick.
Of course metaclasses are not very good for this as they're surprising in other ways
Agreed. Jim -- Jim Fulton http://www.linkedin.com/in/jimfulton
On 03/21/2011 04:08 PM, Jim Fulton wrote:
On Mon, Mar 21, 2011 at 10:38 AM, Martijn Faassen <faassen@startifact.com> wrote:
On 03/21/2011 03:28 PM, Jim Fulton wrote:
I don't know what the right answer is ... at least not yet. :)
In Django and sqlalchemy declarative a meta class is used for this kind of configuration. Since that is subclassing, it implies inheritance, I think.
Ick.
Of course metaclasses are not very good for this as they're surprising in other ways
Agreed.
Martian allows you to use a similar spelling to metaclasses (base classes), without the weird surprises during run-time (after configuration is already done), and without significant import-time side-effects. With megrok.rdb I actually use SQLAlchemy declarative without their declarative metaclass, as the SQLAlchemy folks were kind enough to tweak their config code so I could call it directly from a grokker. Regards, Martijn
On Mon, Mar 21, 2011 at 15:28, Jim Fulton <jim@zope.com> wrote:
This might be OK for @implements and maybe @adapts, which describe behavior, but start feeling wonky to me for something like: @utility.
Well, the wonkyness comes from @utility *not* being inherited, while @implements would be. That could be confusing. It wold be possible to let @utility be inherited if it's done by scanning, though. But under what name in that case? I think that possibly would be even *more* confusing. It may be that decorators isn't the right answer for registration. In that case scanning is an option, unless we simply go for straight imperative configuration. @implementer(IMyFace) @adapter(IMyFoot) class FootInFace(object): def __init__(self, foot, face) self.foot = foot self.face = face component.utility(FootInFace()) It's easy and clear, but has the drawback of encouraging that registration is done on import time, while scanning separates the registration from the definition. I'm not sure how important that is. On Mon, Mar 21, 2011 at 15:36, Martijn Faassen <faassen@startifact.com> wrote:
Do we really care about "the Python world in general"? Is that relevant to the discussion? Can't we just talk about what we care about? The Python world in general uses metaclasses for this kind of stuff, which relies on base classes too, by the way.
Yeah, but that's to a large extent because class decorators still are relatively new. You can't use them if you need to support Python 2.5.
You'll still need a module importing process, as I sketched out elsewhere if you use class decorators, to have the class decorators kick in at all for modules you don't need to import otherwise.
Is that a problem? In the end, no matter what you do, the module needs to be imported. :-) That a utility doesn't get registered unless you import a specific module somewhere in the app doesn't seem like a problem to me. //Lennart
On Mon, 2011-03-21 at 15:53 +0100, Lennart Regebro wrote:
On Mon, Mar 21, 2011 at 15:28, Jim Fulton <jim@zope.com> wrote:
This might be OK for @implements and maybe @adapts, which describe behavior, but start feeling wonky to me for something like: @utility.
Well, the wonkyness comes from @utility *not* being inherited, while @implements would be. That could be confusing.
It wold be possible to let @utility be inherited if it's done by scanning, though. But under what name in that case? I think that possibly would be even *more* confusing.
It may be that decorators isn't the right answer for registration. In that case scanning is an option, unless we simply go for straight imperative configuration.
@implementer(IMyFace) @adapter(IMyFoot) class FootInFace(object): def __init__(self, foot, face) self.foot = foot self.face = face
component.utility(FootInFace())
It's easy and clear, but has the drawback of encouraging that registration is done on import time, while scanning separates the registration from the definition. I'm not sure how important that is.
It's important to me, at least. Registration-on-import effectively requires that there only be a single component registry for all applications in a process. This is often fine for a given deployment, but as a framework strategy it seems very limiting. - C
On 03/21/2011 10:59 AM, Chris McDonough wrote:
On Mon, 2011-03-21 at 15:53 +0100, Lennart Regebro wrote:
It's easy and clear, but has the drawback of encouraging that registration is done on import time, while scanning separates the registration from the definition. I'm not sure how important that is.
It's important to me, at least. Registration-on-import effectively requires that there only be a single component registry for all applications in a process. This is often fine for a given deployment, but as a framework strategy it seems very limiting.
+1. Pyramid has proven (IMHO) that a component registry can be friendly to developers without being global. Shane
On Mon, Mar 21, 2011 at 12:59 PM, Chris McDonough <chrism@plope.com> wrote:
On Mon, 2011-03-21 at 15:53 +0100, Lennart Regebro wrote: ...
It's easy and clear, but has the drawback of encouraging that registration is done on import time, while scanning separates the registration from the definition. I'm not sure how important that is.
It's important to me, at least. Registration-on-import effectively requires that there only be a single component registry for all applications in a process. This is often fine for a given deployment, but as a framework strategy it seems very limiting.
I'll note that this thread started with me saying: "ZTK projects use ZCML too much. Ideally, ZCML should only have to be used when we want to override something." and: "I think we ought to come up with a much cleaner way of defining default configuration." The intent of this thread, for me, was to come up with a cleaner way to define *default* configurations. The scope is narrower than all configuration. I'm thinking of use cases like the ones Tres mentioned where you now use default arguments to queryUtility and queryAdapter. Having a static way to express default configuration in no way prevents you from utilizing local registries, any more than hard coding defaults in calls to component-lookup APIs does. So where do "static" definitions make sense? I think static definitons make sense in library code when you own one of the interfaces, as in Tres' examples. I'm not positive, but I strongly suspect that this situation covers lots of registrations we now do in ZCML. I would argue that static definitions make sense in application code when you're pretty sure how you want to hook things up, although in this case, whether to express these application defaults in Python or ZCML (or whatever) is a matter of taste. (There are also some potential conflict issues that might make doing this sort of configuration statically unattractive.) One could argue about how much can be expressed as a static default configuration. Maybe elimination of all ZCML is too ambitious a goal, but I think we can avoid a lot of ZCML we have now. I'll probably make some concrete proposal at a later time. I trying to avoid saying more in this thread now, but I thought it was important try to be clearer aout what this thread was supposed to be about. Jim -- Jim Fulton http://www.linkedin.com/in/jimfulton
On Mon, 2011-03-21 at 14:13 -0400, Jim Fulton wrote:
On Mon, Mar 21, 2011 at 12:59 PM, Chris McDonough <chrism@plope.com> wrote:
On Mon, 2011-03-21 at 15:53 +0100, Lennart Regebro wrote: ...
It's easy and clear, but has the drawback of encouraging that registration is done on import time, while scanning separates the registration from the definition. I'm not sure how important that is.
It's important to me, at least. Registration-on-import effectively requires that there only be a single component registry for all applications in a process. This is often fine for a given deployment, but as a framework strategy it seems very limiting.
I'll note that this thread started with me saying:
"ZTK projects use ZCML too much. Ideally, ZCML should only have to be used when we want to override something."
and:
"I think we ought to come up with a much cleaner way of defining default configuration."
The intent of this thread, for me, was to come up with a cleaner way to define *default* configurations. The scope is narrower than all configuration. I'm thinking of use cases like the ones Tres mentioned where you now use default arguments to queryUtility and queryAdapter.
Having a static way to express default configuration in no way prevents you from utilizing local registries, any more than hard coding defaults in calls to component-lookup APIs does.
So where do "static" definitions make sense? I think static definitons make sense in library code when you own one of the interfaces, as in Tres' examples. I'm not positive, but I strongly suspect that this situation covers lots of registrations we now do in ZCML.
I would argue that static definitions make sense in application code when you're pretty sure how you want to hook things up, although in this case, whether to express these application defaults in Python or ZCML (or whatever) is a matter of taste. (There are also some potential conflict issues that might make doing this sort of configuration statically unattractive.)
One could argue about how much can be expressed as a static default configuration. Maybe elimination of all ZCML is too ambitious a goal, but I think we can avoid a lot of ZCML we have now.
I'll probably make some concrete proposal at a later time. I trying to avoid saying more in this thread now, but I thought it was important try to be clearer aout what this thread was supposed to be about.
I personally don't see the benefit of externalizing default configuration over having defaults be part of the call-site code as per Tres' example. Having the defaults in the code itself makes it much, much easier for people reading the code to understand the garden path. - C
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 03/21/2011 02:13 PM, Jim Fulton wrote:
On Mon, Mar 21, 2011 at 12:59 PM, Chris McDonough <chrism@plope.com> wrote:
On Mon, 2011-03-21 at 15:53 +0100, Lennart Regebro wrote: ...
It's easy and clear, but has the drawback of encouraging that registration is done on import time, while scanning separates the registration from the definition. I'm not sure how important that is.
It's important to me, at least. Registration-on-import effectively requires that there only be a single component registry for all applications in a process. This is often fine for a given deployment, but as a framework strategy it seems very limiting.
I'll note that this thread started with me saying:
"ZTK projects use ZCML too much. Ideally, ZCML should only have to be used when we want to override something."
and:
"I think we ought to come up with a much cleaner way of defining default configuration."
The intent of this thread, for me, was to come up with a cleaner way to define *default* configurations. The scope is narrower than all configuration. I'm thinking of use cases like the ones Tres mentioned where you now use default arguments to queryUtility and queryAdapter.
Having a static way to express default configuration in no way prevents you from utilizing local registries, any more than hard coding defaults in calls to component-lookup APIs does.
So where do "static" definitions make sense? I think static definitons make sense in library code when you own one of the interfaces, as in Tres' examples. I'm not positive, but I strongly suspect that this situation covers lots of registrations we now do in ZCML.
I would argue that static definitions make sense in application code when you're pretty sure how you want to hook things up, although in this case, whether to express these application defaults in Python or ZCML (or whatever) is a matter of taste. (There are also some potential conflict issues that might make doing this sort of configuration statically unattractive.)
One could argue about how much can be expressed as a static default configuration. Maybe elimination of all ZCML is too ambitious a goal, but I think we can avoid a lot of ZCML we have now.
I'll probably make some concrete proposal at a later time. I trying to avoid saying more in this thread now, but I thought it was important try to be clearer aout what this thread was supposed to be about.
FWIW, I just added 'queryAdapterFactory' and 'queryMultiAdapterFactory' APIs to zope.component on a branch: http://svn.zope.org/zope.component/branches/tseaver-queryAdapterFactory/ These APIs make the "almost never overridden" / dependency injection case as compact for adapters as for utilities. Tres. - -- =================================================================== Tres Seaver +1 540-429-0999 tseaver@palladion.com Palladion Software "Excellence by Design" http://palladion.com -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.10 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAk2HqCYACgkQ+gerLs4ltQ7PFQCgnyoPFi8u8joVkA6wwDEL1ff0 IAcAn1l0s48CLGzVDRsF8tW32If7HCRm =WoQO -----END PGP SIGNATURE-----
On Monday, March 21, 2011, Tres Seaver wrote:
FWIW, I just added 'queryAdapterFactory' and 'queryMultiAdapterFactory' APIs to zope.component on a branch:
http://svn.zope.org/zope.component/branches/tseaver-queryAdapterFactory/
Looks good to me. Incidentally, I was just needing this today and implemented it using sm.adapters.lookup1. Regards, Stephan -- Entrepreneur and Software Geek Google me. "Zope Stephan Richter"
Am 21.03.2011, 20:33 Uhr, schrieb Tres Seaver <tseaver@palladion.com>:
FWIW, I just added 'queryAdapterFactory' and 'queryMultiAdapterFactory' APIs to zope.component on a branch: http://svn.zope.org/zope.component/branches/tseaver-queryAdapterFactory/
These APIs make the "almost never overridden" / dependency injection case as compact for adapters as for utilities.
This is an excellent suggestion! Being able to supply a default implementation that is easily overridden by a configuration is clean and easily understood and avoids both clumsy code checking and configuration. Charlie -- Charlie Clark Managing Director Clark Consulting & Research German Office Helmholtzstr. 20 Düsseldorf D- 40215 Tel: +49-211-600-3657 Mobile: +49-178-782-6226
Hi there, It is my understanding that the ZTK is primarily to adopt proven solutions that arise from our community that we intend to be shared by this community. I'll also note that Martian is already in use in combination with Bluebream, Zope 2 and Grok. With that, I was thinking, concerning alternative configuration mechanisms I think we should have: * a clear statement of use cases concerning configuration. * a weighing of the importance of these use cases, underlying reasons, etc. * an evaluation whether Martian or Venusian can fulfill those use cases. * if use cases are found that Martian or Venusian cannot fulfill, then an evaluation whether these can be made to fulfill them by modifying them. Regards, Martijn
On 03/21/2011 03:07 PM, Lennart Regebro wrote:
On Mon, Mar 21, 2011 at 14:17, Martijn Faassen<faassen@startifact.com> wrote:
Anyway, Grok's configuration is dependent on the rules you give it, so it's possible to have a completely explicit set of directives with no implicit fallback to default values whatsoever. Martian supports that scenario right now.
Sure, but I'm of course referring to how it behaves by default, including the associations made by the grokking process. I think that makes sense in a webapp, but not otherwise, and we should at least as a start concentrate on the generic component architecture case.
For Martian, Python 3 support is mostly an issue of making class directives work as class decorators.
And the same goes for zope.component support, of course.
We have a much more extensive directive abstraction though. That should make it harder and easier at the same time. :)
With martian, the registration is then done by the grokking process, but I think decorators would be a process that is more acceptable to the Python world in general. Instances does indeed require something else than decorators, I hadn't thought of that, that's a drawback.
Do we really care about "the Python world in general"? Is that relevant to the discussion? Can't we just talk about what we care about? The Python world in general uses metaclasses for this kind of stuff, which relies on base classes too, by the way. You'll still need a module importing process, as I sketched out elsewhere, if you use class decorators, to have the class decorators kick in at all for modules you don't need to import otherwise. Note that we do support function decorators with Martian. Regards, Martijn
participants (8)
-
Charlie Clark -
Chris McDonough -
Jim Fulton -
Lennart Regebro -
Martijn Faassen -
Shane Hathaway -
Stephan Richter -
Tres Seaver