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

Preliminary step to deprecate docstring based publication control #1197

Merged
merged 12 commits into from
Feb 24, 2024

Conversation

d-maurer
Copy link
Contributor

@d-maurer d-maurer commented Feb 17, 2024

This PR addresses #774.

It introduces the decorator ZPublisher.zpublish to explicitly mark a class, method or function definition as designed to be publishable by ZPublisher. If a class is marked in this way, its instances become publishable.

In the long term, zpublish decoration should replace the implicit publication control via the existence of a docstring. If the environment variable ZPUBLISHER_DEPRECATE_DOCSTRINGS has a non-empty value, then a DocstringWarning (derived from DeprecationWarning) is issued when a object gets published because it has a docstring. This should help component authors to find locations where explicit zpublish decoration may become necessary in the future. If ZPUBLISHER_DEPRECATE_DOCSTRINGS has a non-empty value and Python's warning filter does not yet have a special rule for ZPublisher.BaseRequest.DocstringWarning, then such a rule is added. If the value is a meaningful filter action, then it, otherwise "default", is used as action for this rule.

Typically, zpublish is used to mark a class or function/method as designed for publication. However, a decoration with zpublish(False) explicitly marks the class (more precisely its instances) or function/method explicitly as not publishable, overriding a potentially existing docstring.
Following the review suggestion from @perrinjerome, zpublish(methods=...) where ... is either a single request method name or a sequence of request method names marks a method as publishable only for the mentioned request methods.

The full signature of zpublish is publish=True, *, methods=None. If methods in not None, publish must be True. In this case, methods is either a single request method name or a sequence of request method names; those request method names specify the request methods for which the object is publishable.

ZPublisher contains the related functions zpublish_marked and zpublish_wrap. zpublish_marked checks whether there is a zpublishable mark for an object, zpublish_wrap can return a zpublishable signature preserving wrapper for a callable that lacks its own zpublishable mark. This is used by registerClass to automatically add zpublishable marks to a registered class and its constructors.

zpublish uses an attribute for publication control. Therefore, derived classes inherit the control by default, unlike as for docstrings.
Another consequence is that (other) decorators must preserve attributes of a decorated function; otherwise, the attribute set by zpublish may get lost.
Currently, the decorator AccessControl.requestmethod.requestmethod does not fulfill this requirement.

@d-maurer d-maurer marked this pull request as ready for review February 17, 2024 09:40
Copy link
Contributor

@perrinjerome perrinjerome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's really good to see this happening !

Did you consider also defining the request methods in zpublish ? something similar to this:

@zpublish(methods=('PUT', 'POST'))
def only_publishable_for_put_and_post_methods(self):
  ...

@zpublish
def publishable_for_any_methods(self):
  ...

This would be a bit similar to how routes are configured in flask ( https://flask.palletsprojects.com/en/3.0.x/quickstart/#http-methods ). "this is publishable" and "this is publishable, but only for these http methods" are very related concepts, it might make sense to define the two at the same time. This would probably deprecate AccessControl.requestmethod.requestmethod.

CHANGES.rst Outdated Show resolved Hide resolved
src/webdav/Resource.py Outdated Show resolved Hide resolved
@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 17, 2024 via email

@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 18, 2024 via email

dataflake
dataflake previously approved these changes Feb 20, 2024
Copy link
Member

@dataflake dataflake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this stuff. I always thought the docstring method was too obscure and contrived. It led to many methods and functions with empty or nonsense docstrings, just to trigger the publisher. The decorator is very explicit and easy to understand.

src/ZPublisher/BaseRequest.py Show resolved Hide resolved
src/ZPublisher/__init__.py Outdated Show resolved Hide resolved
@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 20, 2024 via email

dataflake
dataflake previously approved these changes Feb 20, 2024
perrinjerome
perrinjerome previously approved these changes Feb 21, 2024
Copy link
Contributor

@perrinjerome perrinjerome left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great !

I tried a bit with ZPUBLISHER_DEPRECATE_DOCSTRINGS enabled and I noticed that some places in Zope are still using docstring based publication:


Is there already a plan regarding docstring deprecation and ZPUBLISHER_DEPRECATE_DOCSTRINGS ? I imagine it could be something like:

  • Next Zope 5 introduces ZPublisher.zpublish, allows both objects marked with ZPublisher.zpublish or with a docstring (with an optional warning if ZPUBLISHER_DEPRECATE_DOCSTRINGS is set)
  • Zope and "main" packages are updated to use ZPublisher.zpublish, once this is done ZPUBLISHER_DEPRECATE_DOCSTRINGS is removed and warnings are issued every time (users can set a warnings filter if it's a problem)
  • Zope 6 only allows objects marked with ZPublisher.zpublish.

About the warning itself, I see there is one warning for each URL, so if the same object is accessed from different places it will cause a warning each time. Maybe the warning should be not by URL but by object ? If we choose this, I don't know what's the best way, if we just str() the object it might be an acquisition wrapper which will have the acquisition chain ( something like <PythonScript at /script_python used for /folder> ) which would cause lots of different warnings, so
I was thinking maybe we need to special case the case of a method to print the class and the method. I'm not sure what would be the best way of doing this. I tried (just a little bit so this might be wrong) and this might be a starting point:

diff --git a/src/ZPublisher/BaseRequest.py b/src/ZPublisher/BaseRequest.py
index bce4601e0..63b76ab56 100644
--- a/src/ZPublisher/BaseRequest.py
+++ b/src/ZPublisher/BaseRequest.py
@@ -718,7 +718,7 @@ class BaseRequest:
                 "to `ZPublisher.zpublish` decorator or have a docstring to be "
                 "published.")
         if deprecate_docstrings:
-            warn(DocstringWarning(url))
+            warn(DocstringWarning(obj))
 
 
 def exec_callables(callables):
@@ -818,6 +818,7 @@ deprecate_docstrings = environ.get("ZPUBLISHER_DEPRECATE_DOCSTRINGS")
 
 class DocstringWarning(DeprecationWarning):
     def __str__(self):
-        return (f"The object at {self.args[0]} uses deprecated docstring "
-                "publication control. Use the `ZPublisher.zpublish` decorator "
-                "instead")
+        published = self.args[0]
+        return (f"{published.__module__}.{published.__qualname__} was published because it uses "
+                "deprecated docstring publication control. "
+                "Mark it publishable with `ZPublisher.zpublish` decorator instead.")

The PR says Preliminary support, all this can be addressed later maybe, this is already good as it is, thanks again.

@icemac icemac removed their request for review February 21, 2024 07:27
@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 21, 2024 via email

@d-maurer d-maurer dismissed stale reviews from perrinjerome and dataflake via 333507c February 22, 2024 09:16
@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 22, 2024 via email

@dataflake
Copy link
Member

Question: Many places that have a zpublish decorator also have a security management decorator. Some places zpublish comes first, other places the security management decorator comes first. What's the best order? Does it matter?

@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 22, 2024 via email

@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 23, 2024 via email

@d-maurer
Copy link
Contributor Author

Apparently, additional commits after a review invalidate the review (which is reasonable). Therefore, I will rerequest reviews.
From my point of view, the PR is ready to be merged.

@d-maurer d-maurer merged commit 3b32e2e into master Feb 24, 2024
26 checks passed
@d-maurer d-maurer deleted the zpublish branch February 24, 2024 10:31
@perrinjerome
Copy link
Contributor

@d-maurer I could not reply earlier, but I looked again and everything looks perfect, thank you !

@d-maurer
Copy link
Contributor Author

d-maurer commented Feb 25, 2024 via email

@davisagli
Copy link
Member

@d-maurer Thank you for your work on this. It was one of my biggest frustrations with Zope.

@mauritsvanrees We should check how this affects Plone.

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

Successfully merging this pull request may close these issues.

4 participants