Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecating docstring control: Zope Start may get blocked by Products #1202

Closed
drfho opened this issue Apr 2, 2024 · 14 comments
Closed

Deprecating docstring control: Zope Start may get blocked by Products #1202

drfho opened this issue Apr 2, 2024 · 14 comments
Assignees

Comments

@drfho
Copy link
Contributor

drfho commented Apr 2, 2024

Hello @d-maurer,
I am sorry to report the following error launching zope:
after merging "Preliminary step to deprecate docstring based publication control" #1197 into main-branch 3b32e2e Zope fails starting with a type error calling Products.CMFCore

I am not sure where the problem may be located:
a bug due to the code-change of Zope or Products.CMFCore must get compatible with the new code?
Any hints/comments?

Log

~/vpy38_zms/bin$ runwsgi -v ~/instance/zms5_main/etc/zope.ini
__REGISTRY__['confdict'] None
2024-04-02 22:12:54 INFO [chameleon.config:39][MainThread] directory cache: /home/zope/instance/zms5_main/var/cache.
Traceback (most recent call last):
  File "/home/zope/vpy38_zms/bin/runwsgi", line 8, in <module>
    sys.exit(main())
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/Startup/serve.py", line 251, in main
    return command.run()
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/Startup/serve.py", line 189, in run
    app = self.loadapp(app_spec, name=app_name, relative_to=base,
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/Startup/serve.py", line 220, in loadapp
    return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/loadwsgi.py", line 246, in loadapp
    return loadobj(APP, uri, name=name, **kw)
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/loadwsgi.py", line 271, in loadobj
    return context.create()
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/loadwsgi.py", line 738, in create
    return self.object_type.invoke(self)
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/loadwsgi.py", line 198, in invoke
    app = context.app_context.create()
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/loadwsgi.py", line 738, in create
    return self.object_type.invoke(self)
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/loadwsgi.py", line 136, in invoke
    return fix_call(context.object, context.global_conf, **context.local_conf)
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/util.py", line 64, in fix_call
    raise exc_info[1] from None
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/paste/deploy/util.py", line 61, in fix_call
    val = callable(*args, **kw)
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/Startup/run.py", line 61, in make_wsgi_app
    starter.prepare()
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/Startup/starter.py", line 38, in prepare
    self.startZope()
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/Startup/starter.py", line 94, in startZope
    Zope2.startup_wsgi()
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/__init__.py", line 36, in startup_wsgi
    _startup()
  File "/home/zope/src/zopefoundation/Zope/src/Zope2/App/startup.py", line 136, in startup
    OFS.Application.initialize(application)
  File "/home/zope/src/zopefoundation/Zope/src/OFS/Application.py", line 261, in initialize
    initializer.initialize()
  File "/home/zope/src/zopefoundation/Zope/src/OFS/Application.py", line 283, in initialize
    self.install_products()
  File "/home/zope/src/zopefoundation/Zope/src/OFS/Application.py", line 365, in install_products
    return install_products(self.getApp())
  File "/home/zope/src/zopefoundation/Zope/src/OFS/Application.py", line 389, in install_products
    install_product(app, product_dir, product_name, meta_types,
  File "/home/zope/src/zopefoundation/Zope/src/OFS/Application.py", line 493, in install_product
    initmethod(context)
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/Products/CMFCore/__init__.py", line 129, in initialize
    utils.ToolInit('CMF Core Tool', tools=tools,
  File "/home/zope/vpy38_zms/lib/python3.8/site-packages/Products/CMFCore/utils.py", line 602, in initialize
    context.registerClass(
  File "/home/zope/src/zopefoundation/Zope/src/App/ProductContext.py", line 206, in registerClass
    m[name] = zpublish_wrap(method)
  File "/home/zope/src/zopefoundation/Zope/src/ZPublisher/__init__.py", line 152, in zpublish_wrap
    sig = signature(callable)
  File "/usr/lib/python3.8/inspect.py", line 3105, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
  File "/usr/lib/python3.8/inspect.py", line 2854, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
  File "/usr/lib/python3.8/inspect.py", line 2228, in _signature_from_callable
    raise TypeError('{!r} is not a callable object'.format(obj))
TypeError: <Products.CMFCore.utils.ToolInit object at 0x7f03f136c2e0> is not a callable object

List of Versions

Package                        Version    Editable project location
------------------------------ ---------- ----------------------------------
AccessControl                  6.3
Acquisition                    5.2
AuthEncoding                   5.0
beautifulsoup4                 4.12.3
BTrees                         5.2
certifi                        2024.2.2
cffi                           1.16.0
Chameleon                      4.4.4
charset-normalizer             3.3.2
configparser                   6.0.1
DateTime                       5.5
DocumentTemplate               4.6
docutils                       0.20.1
ExtensionClass                 5.1
five.localsitemanager          4.0
idna                           3.6
importlib_metadata             7.1.0
importlib_resources            6.4.0
Missing                        5.0
MultiMapping                   5.0
multipart                      0.2.4
Paste                          3.8.0
PasteDeploy                    3.1.0
Persistence                    4.1
persistent                     5.2
pillow                         10.3.0
pip                            24.0
pkg_resources                  0.0.0
Products.BTreeFolder2          5.1
Products.CMFCore               3.3
Products.ExternalMethod        5.0
Products.GenericSetup          3.0.2
Products.MailHost              5.2
Products.PythonScripts         5.0
Products.Sessions              5.0
Products.SiteErrorLog          6.0
Products.StandardCacheManagers 5.0
Products.TemporaryFolder       7.0
Products.ZCatalog              7.1
Products.ZODBMountPoint        2.0
pycparser                      2.22
python-gettext                 5.0
pytz                           2024.1
Record                         4.1
requests                       2.31.0
RestrictedPython               7.2a1.dev0
roman                          4.1
setuptools                     44.0.0
six                            1.16.0
soupsieve                      2.5
tempstorage                    6.0
transaction                    4.0
urllib3                        2.2.1
waitress                       3.0.0
WebOb                          1.8.7
WebTest                        3.0.0
WSGIProxy2                     0.5.1
xmltodict                      0.13.0
z3c.pt                         4.3
zc.lockfile                    3.0.post1
ZConfig                        4.0
zExceptions                    5.0
zipp                           3.18.1
ZMS                            5.2.0
ZODB                           6.0
zodbpickle                     3.2
Zope                           5.9.1.dev0 /home/zope/src/zopefoundation/Zope
zope.annotation                5.0
zope.app.publication           5.0
zope.authentication            5.0
zope.browser                   3.0
zope.browsermenu               5.0
zope.browserpage               5.0
zope.browserresource           5.1
zope.cachedescriptors          5.0
zope.component                 6.0
zope.configuration             5.0.1
zope.container                 5.2
zope.contentprovider           5.0
zope.contenttype               5.1
zope.datetime                  5.0.0
zope.deferredimport            5.0
zope.deprecation               5.0
zope.dottedname                6.0
zope.error                     5.0
zope.event                     5.0
zope.exceptions                5.0.1
zope.filerepresentation        6.0
zope.globalrequest             2.0
zope.hookable                  6.0
zope.i18n                      5.1
zope.i18nmessageid             6.1.0
zope.interface                 6.2
zope.lifecycleevent            5.0
zope.location                  5.0
zope.pagetemplate              5.1
zope.processlifetime           3.0
zope.proxy                     5.2
zope.ptresource                5.0
zope.publisher                 7.0
zope.schema                    7.0.1
zope.security                  6.2
zope.sendmail                  6.2
zope.sequencesort              5.0
zope.site                      5.0
zope.size                      5.0
zope.structuredtext            5.0
zope.tal                       5.0.1
zope.tales                     6.0
zope.testbrowser               6.0
zope.testing                   5.0.1
zope.traversing                5.0
zope.untrustedpython           6.0
zope.viewlet                   5.0
ZopeUndo                       6.0

@d-maurer
Copy link
Contributor

d-maurer commented Apr 3, 2024 via email

@mauritsvanrees
Copy link
Member

I see the same in a Plone 6.0.10 site when I use a checkout of Zope master. (Actually a different branch of a PR that I am testing.) When I do a try/except to swallow the error and add some printing, I see the error happens several times:

Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f595dac5fd0>
Swallow ERROR wrapping name='contentinit' method=<Products.CMFCore.utils.ContentInit object at 0x7f595dac5fd0>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f595dac5fd0>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f59697b5e10>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f595dad1bd0>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFPlone.utils.ToolInit object at 0x7f596a389c50>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f595ff16990>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f5970088390>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f59697fcf90>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f595fe0ead0>
Swallow ERROR wrapping name='toolinit' method=<Products.CMFCore.utils.ToolInit object at 0x7f59601c8490>

So:

  • most are for Products.CMFCore.utils.ToolInit
  • one for Products.CMFCore.utils.ContentInit
  • one for Products.CMFPlone.utils.ToolInit

@d-maurer
Copy link
Contributor

I have looked into this problem: likely it comes from an abuse of the constructors parameter of App.ProductContext.ProductContext.registerClass in Products.CMFCore.utils.ToolInit.initialize which registers a (non callable) ToolInit instance as constructor. But the term "constructor" entails being callable.

The problem (but not the abuse) will disappear once ToolInit has got its @zpublish decoration (because zpublish_wrap wraps only if a zpublish mark is still missing).

I assume that the constructors parameter was abused because it allows to put something into the ProductContext's namespace. Formerly, it was not relevant whether this was really a constructor (and therefore callable); but this has now changed. We could add a new registerClass parameter for this purpose avoiding the need of a constructors abuse.

I recommend a new branch for Products.CMFCore to use zpublish decoration. We would then use this branch (or one of its successors) to use Products.CMFCore together with Zope master.

@d-maurer
Copy link
Contributor

The Products.CMFCore.utils.CointentInit problem (and likely also Products,CMFPlone.utils.ToolInit) is identical: something non callable passed as constructors component.

An alternative approach could be to issue a warning when ZPublisher.zpublish_wrap is called with a non callable and return it in this case unwrapped (rather than wrapped).

@drfho
Copy link
Contributor Author

drfho commented Apr 22, 2024

Thank you very much @d-maurer for your detailed feedback.
Let me introduce a different perspective:

  1. The discussion 4 years ago Deprecate that docstrings influence access rights #774
    seems to deal with a somehow optional problem (otherwise it wouldn't have taken 4 years until you dealt with that, right?).
  2. And now, when adding the change to Zope main branch, maybe the prohibitive consequences to very crucial components that often motivate the usage of Zope (like Plone or ZMS) were not obvious. But now we know that important multipiers like Plone or ZMS may get blocked from using the Zope version 5.9.1.
  3. So, instead of just cutting off important components like CMFCore (and even other Products?) it might be more appropriate to shift this Zope change back to its branch or adding backwards compatibility to the new code, maybe generating a log info like "will be soon deprecated".

Looking forward to any comments.

@d-maurer
Copy link
Contributor

d-maurer commented Apr 22, 2024 via email

@d-maurer
Copy link
Contributor

I created #1205 to provide a work around for the design bug in Products.CMFCore and Products.CMFPlone (registering non callable constructors with App.ProductContext.ProductsContext.registerClass). The real fixes should be done in those packages.

@mauritsvanrees
Copy link
Member

I have a draft PR in Plone to test with a checkout of Zope and using its versions.cfg:
plone/buildout.coredev#920

I already see some changes are needed because some tests use a mock version of a request that has no ensure_publishable method. We will have to see whether we make Zope more forgiving here, or if we need to change tests in Plone.

mauritsvanrees added a commit to zopefoundation/Products.CMFCore that referenced this issue Apr 23, 2024
This avoids a deprecation warning for using a non callable constructor in Zope higher than 5.9.
See report in zopefoundation/Zope#1202
@mauritsvanrees
Copy link
Member

The full Plone 6.0 test suite is finished, and the good news is that only the plone.dexterity tests fail, as I already saw:
https://jenkins.plone.org/job/pull-request-6.0-3.11/1185/

I would be in favour of my Zope PR to make Zope more lenient here, because I expect several Zope/Plone add-ons will have the same problem. But this case is easy to fix in plone.dexterity, so that is also an option.

@d-maurer
Copy link
Contributor

d-maurer commented Apr 24, 2024 via email

@mauritsvanrees
Copy link
Member

Disadvantage: Formerly, the publication control has been part of DefaultPublishTraverse, i.e. an application could have changed it by registering an adapter; this will be lost - i.e. we introduce an incompatibility.

Plone has dozens of places where an IDefaultPublishTraverse adapter is implemented. For example here in plone.namedfile the publishTraverse method is used to get an image or a scale of an image. So if you mean you want to remove support for defining an IDefaultPublishTraverse, this would break Plone in lots of places.

You are right though that it is likely that ensure_publishable never gets called in this case. That is perfectly fine in plone.namedfile, because the images and scales are very much meant to be published, but there may be other cases where we do want the check. I have good hope though that in all current cases in Plone, all such adapters already only return objects that are really meant for being published.

I doubt if moving the check to BaseRequest.traverse is good: that sounds like the publishable check would be done whenever any code traverses to an item, even when this is not the item that would in the end get published.
But maybe that case follows a completely different code path. For clarity let me explain what I mean by this case, because it is easy to have a different understanding:

  • The browser requests localhost:8080/Plone/statistics_view.
  • statistics_view is a browser view that I just made up, and for this view ensure_publishable gets called. Good.
  • This browser view has a __call__ method that does this: total = len(self.context.restrictedTraverse("portal_catalog").getAllBrains()); return f"total items: {total}". This line does traversal, but here we should not call ensure_publishable, because the portal_catalog is not the item that gets published.

Would it make sense to move ensure_publishable to the adapters, and call it in BaseRequest.traverseName after calling publishTraverse? So something like this:

        if IPublishTraverse.providedBy(ob):
            ob2 = ob.publishTraverse(self, name)
            if hasattr(ob, "ensure_publishable"):
                ob.ensure_publishable(ob2)
        else:
            adapter = queryMultiAdapter((ob, self), IPublishTraverse)
            if adapter is None:
                adapter = DefaultPublishTraverse(ob, self)
            ob2 = adapter.publishTraverse(self, name)
            if hasattr(adapter, "ensure_publishable"):
                adapter.ensure_publishable(ob2)

Maybe fall back to calling DefaultPublishTraverse.ensure_publishable if this method is not defined on ob or adapter.

My main fear is that changes are being introduced in Zope that seem fine at first glance, and are needed in the long run, but that end up breaking stuff, forcing Plone 6.0 to stick to Zope 5.9 for a long time until the dust has settled, or maybe even forever when too much breaks. Current master seems okay now though, after recent fixes from you and others.

@d-maurer
Copy link
Contributor

d-maurer commented Apr 24, 2024 via email

@mauritsvanrees
Copy link
Member

Of course, I do not mean to remove support for IPublishTraverse adaptation: I want to move the publication control (i.e. currently the "has docstring"-, later "is marked for publication"-check) out of it into BaseRequest.traverse.

Understood now. I misinterpreted a comment.

In your referenced plone.namedfile adaptation ("https://github.com/plone/plone.namedfile/blob/6.3.0/plone/namedfile/scaling.py#L442-L475"), I see that you have removed the publication control (i.e. the "has docstring" test). Was this "by purpose" (i.e. you do not want any publication control) or accidentally (publication control is sumewhat orthogonal to traversal and therefore easily forgotten)? The first case would speak against, the latter for my proposal.

Hard to say. Maybe a bit of both. At least whatever we return is always some browser view that either returns an original image or a scale, so they are always meant to be published.

In plone.restapi I see a lot of cases like this one, where the traversal code consumes a name from the url, and then returns self, in which case publication control is not needed:

    def publishTraverse(self, request, name):
        # Consume any path segments after /@users as parameters
        self.params.append(name)
        return self

I suspect this is true in 99% of the cases in core Plone: in a publishTraverse override publication control is for various reasons not needed. But if Zope changes something so the publication is checked always, then for these cases I would expect it takes a few unnecessary CPU cycles, but nothing would break.

Yes, but that is precisely the case since ages -- the "docstring requirement" does not apply only to the final published object but to any traversed object (unless an IPublishTraverse adapter says something else). You likely are not aware of this because almost all container objects have a docstring.

Okay, I did not know that. There is always something new to learn in Zope. Thanks.

You already have created a plone.dexterity PR to resolve its _ensure_publishable related test problems -- and reported that the other Plone components have no such problems. Thus, there is no need for a change in Zope at the moment. Maybe, we consider my proposal (ensure publication control is active by default, independent of traversal related adaptation) for Zope 6. We a new major version, some incompatibilities may be acceptable.

That sounds good to me.

Thanks for your thoughts and fixes @d-maurer and others. I think we can close this issue now.

@d-maurer
Copy link
Contributor

d-maurer commented Apr 24, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants