python / cpython

The Python programming language
https://www.python.org
Other
62.17k stars 29.89k forks source link

Docs: make it easier to reference and markup decorators #105812

Open erlend-aasland opened 1 year ago

erlend-aasland commented 1 year ago

_Originally posted by @erlend-aasland in https://github.com/python/cpython/pull/105792#discussion_r1230584709_

It would be nice if we had a "decorator role" that used the Python function/method markup, but instead of adding trailing parens, added a prefixing '@': :decorator:`dataclasses.dataclass` => @dataclasses.dataclass.

There's already decorator and decoratormethod directives for signatures. Perhaps we can exploit these so the :py:meth: and py:func: roles adapt the format according to what directive is used. A role could be a nice supplement, though; sometimes we want to explicitly refer to a decorator as a function or the other way around.

Linked PRs

erlend-aasland commented 1 year ago

https://github.com/python/cpython/blob/da911a6b226ca47cc15088d800b575e19a731f1c/Doc/tools/extensions/pyspecific.py#L713-L714

https://github.com/python/cpython/blob/da911a6b226ca47cc15088d800b575e19a731f1c/Doc/tools/extensions/pyspecific.py#L345-L356

AlexWaygood commented 1 year ago

I really like the idea of having a py:deco: role for marking up decorators. There's lots of docs PRs I would have used this on in the past; I think it would be very useful. When discussing decorators, I usually want the @ prefixed before the decorator name, and I usually don't want the () after the decorator name that the py:func: role adds.

I'm not sure how I feel about py:func: and py:meth: having different rendering depending on whether the function/method being linked to is a decorator or not. That feels like it might be somewhat surprising behaviour for people new to it?

CAM-Gerlach commented 1 year ago

A :py:deco: role seems most reasonable to me, as it is most consistent with how other multiple roles that mark up and style objects from a single namespace differently depending on the intended semantics operate, and it would also likely be a lot simpler, more robust and more efficient to implement than trying to monkeypatch the :py:func: and :py:meth: roles. It could also potentially get pulled into upstream Sphinx, if @AA-Turner is interested.

To note, Sphinx provides built-in enhanced versions of those two directives, so we could probably dump the custom overrides and switched to the fuller-featured, standard versions instead. It wouldn't really change anything either way with a :py:deco: role, though, since the lookup is ultimately the same.

erlend-aasland commented 1 year ago

I'm not sure how I feel about py:func: and py:meth: having different rendering depending on whether the function/method being linked to is a decorator or not. That feels like it might be somewhat surprising behaviour for people new to it?

I agree. Explicit is better than implicit.

CAM-Gerlach commented 5 months ago

Implementation-wise, it could be as simple as app.add_role_to_domain(domain="py", name="deco", role=sphinx.domains.python.PyXRefRole) if you don't need custom display behavior (e.g. prepending a @, or not appending ()).

If you do want that, you should be able to just subclass PyXRefRole, wrap process_link to prepend a @ to the returned title (for instance), and then just pass your subclassed PyXRefRole instead of the Sphinx one.

Of course, this doesn't actually check that the target is actually a decorator, but neither do the other :py: roles generally warn if their object types don't match (as they are all stored in the same index). You could implement something like that by modifying the custom decorator directive to add/store some sort of indicator metadata or a list of decorators, and then in process_link check against that list and emit a Sphinx warning if it doesn't match, but not required for an initial implementation.