FYI: Dexterity behaviors and (direct) attribute access explained

For some reason in yesterdays framework-team (FWT) meeting we had some confusion about direct attribute access in context of behaviors. So if the FWT-members are confused, what about all the poor add-on developers out there... :sunglasses:

I try to reduce the amount of myths and put in some facts:

First we have two kinds of behaviors:

  1. with schema only (usually a form-field-provider)
  2. with adapter factory

(further reading: plone.behavior README.rst, all, but focus on examples zcml-reference part)


In case (1) schema only all form fields will be store directly on the context.

This has advantages

  • get values by direct attribute access on context
  • default values are provided on attribute access too
  • fast access if value is already set
  • one does not need to know name of provided interface/schema

and disadvantages:

  • no name-spaces: if two behaviors are sharing an attribute it is kind of black-magic what happens (however: first wins)
  • slow access of default: in code it iterates over all behaviors, inspect schemas and then returns default. this probably slows down access in acquisition chains (needs profiling to get numbers).

in case (2) with adapter factory there is no attribute access provided.

Adapters are used if the values need more love than just storing them, also a behavior may read values form a different place or just provides calculations.

So the lookup of the behavior need to be done with the given provides (in zcml) interface/schema. Alternatively a lookup by dotted name (identifier - also provides in zcml) is possible, even if this is not very easy in code.

Btw.: This works also with example (1) schema-only, because the context itself is returned if no factory is given.

In any way one must know the complex dotted name in order to import or resolve, example:

from collective.whatever.module.behaviors import ISomeCustomBehavior
... 
behavior_adapter = ISomeCustomBehavior(context)
value = behavior_adapter.someattribute

advantages:

  • very explicit
  • no name-space collisions

disadvantages

  • need to proxy all attributes to storage (on context)

  • loooong names to remember

  • nearly impossible to quickly introspect in debugger

  • access slower (lookup needed always)

3 Likes

Shouldn't that be

behavior_adapter = ISomeCustomBehavior(context)    
value = behavior_adapter.someattribute

?

To me, the most important message is that if you're writing a behavior that you want to be generally useful, you need to document your attribute access.

And how does this work with ttw code?

That's why the Dexterity programming model is broken by design.

https://www.andreas-jung.com/contents/plone-the-broken-parts-non-pythonic-programming-model

ops, sure, i'll correct it

@djay direct attributes access works, because dexterity has its own permission validator respecting fields read permission:


Write permissions are not covered (so Archetypes did a better job here).

I doubt behavior lookup by adapter factory is possible TTW. We then would need an defined API looking up behaviors.
Once adapter is looked up, permissions checking here is upon the custom adapter and not in the domain of core dexterity.

I remember running into this very issue with TTW attribute access. It's confusing to non-core types like me why sometimes it will work and sometimes it won't. @jensens, your explanation is great but it's not something we can tell regular people. :slight_smile:

1 Like

its a shame the design of dexterity didn't take restrictedpython into account. I can't see an easy way out other than lots and lots of documentation explaining how do do things TTW next to how to do them via the filesystem. At the moment most of our documentation assumes filesystem access which is even more confusion.