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

Add Python synopsis and parameter cross-reference support #80

Merged
merged 3 commits into from
Apr 30, 2022

Conversation

jbms
Copy link
Owner

@jbms jbms commented Apr 28, 2022

Note: This still requires some work --- this does not preserve parameter types specified outside of the signature, and does not gracefully handle invalid Python syntax as signatures, e.g. using brackets to indicate optional parameters.

This was missed in the refactoring that added the
format_object_description_tooltip function.
@jbms jbms requested a review from 2bndy5 April 28, 2022 22:07
@2bndy5
Copy link
Collaborator

2bndy5 commented Apr 29, 2022

I'm getting a conflict about confusion between a c'tor parameter that has the same name as a class attribute.

.. autoclass:: RF24Mesh

   :param int node_id: blah blah blah

.. autoattribute:: RF24Mesh.node_id

simplified py src

class RF24Mesh:
    def __init__(self, node_id: int):
        self._node_id = node_id  # set node_id attribute

    @property
    def node_id(self) -> int:
       return self._node_id

Error output in build log is

path\to\circuitpython_nrf24l01\rf24_mesh.py:docstring of circuitpython_nrf24l01.rf24_mesh.RF24Mesh.node_id:1: WARNING: duplicate object description of circuitpython_nrf24l01.rf24_mesh.RF24Mesh.node_id, other instance in network_docs/mesh_api, use :noindex: for one of them

This also seems to cause some confusion about which member to use when generating the synopsis in this instance. I noticed the synopsis for the parameter uses the attribute's docstring, but all links to attribute lead to the class c'tor param (probably because it is first in the toc).


I also got a warning related to using autofunction for a module-level function instead of automethod (which seems intended for class functions).

path\to\circuitpython_nrf24l01\network\structs.py:docstring of circuitpython_nrf24l01.network.structs.is_address_valid:: WARNING: Parameter name 'address' does not match any of the parameters defined in the signature: []

py src code:

"""circuitpython_nrf24l01.network.structs.py"""

def is_address_valid(address) -> bool:
    # do logic
    return address or False

documented as

.. automethod:: circuitpython_nrf24l01.network.structs.is_address_valid

   Docstr for function.

   :param address: blah blah blah

Using autofunction instead of automethod fixes this for documented module-level functions. It seems to be a lack of intuition on behalf of the autodoc ext.


On a positive note, these changes have alerted me about documented param names that are inconsistent with the actual param names in the signature 👍🏼

.. py:function:: some_func(param_name)

    :param incorrect_param_name: blah blah blah

This is mostly because I keep the majority of my docstrings - though not entire doctrings - in rst files (separate from py src code files) to save on storage space in microcontrollers.

@jbms jbms force-pushed the feat-python-parameter-xrefs branch from e066428 to a50e000 Compare April 29, 2022 17:46
@jbms
Copy link
Owner Author

jbms commented Apr 29, 2022

I'm getting a conflict about confusion between a c'tor parameter that has the same name as a class attribute.
Error output in build log is

path\to\circuitpython_nrf24l01\rf24_mesh.py:docstring of circuitpython_nrf24l01.rf24_mesh.RF24Mesh.node_id:1: WARNING: duplicate object description of circuitpython_nrf24l01.rf24_mesh.RF24Mesh.node_id, other instance in network_docs/mesh_api, use :noindex: for one of them

This is a good point --- I'm not sure what the best solution is here.

Currently I am assigning parameters the full name: <parent>.<param-name>. That works fine for functions since they can't have any other "child" symbols. But when documenting the class constructor at the class-level rather than as a separate __init__ method we run into a conflict.

Personally I think it is better to document constructors as separate __init__ methods (and e.g. in TensorStore I then apply some additional transforms to display them better: https://google.github.io/tensorstore/python/api/tensorstore.Context.html#constructors), but I understand it is pretty common to document class constructors within the class docstring itself.

This also seems to cause some confusion about which member to use when generating the synopsis in this instance. I noticed the synopsis for the parameter uses the attribute's docstring, but all links to attribute lead to the class c'tor param (probably because it is first in the toc).

I also got a warning related to using autofunction for a module-level function instead of automethod (which seems intended for class functions).

path\to\circuitpython_nrf24l01\network\structs.py:docstring of circuitpython_nrf24l01.network.structs.is_address_valid:: WARNING: Parameter name 'address' does not match any of the parameters defined in the signature: []

py src code:

"""circuitpython_nrf24l01.network.structs.py"""

def is_address_valid(address) -> bool:
    # do logic
    return address or False

documented as

.. automethod:: circuitpython_nrf24l01.network.structs.is_address_valid

   Docstr for function.

   :param address: blah blah blah

Using autofunction instead of automethod fixes this for documented module-level functions. It seems to be a lack of intuition on behalf of the autodoc ext.

I'm a bit confused --- that looks like a function, not a method. Is the error happening when using autofunction or when using automethod?

Still it is kind of odd that it would make a difference --- seems like autodoc or the Python domain may be treating the first parameter (i.e. the "self" parameter) specially.

Can you reproduce the issue using the plain Python domain, rather than autodoc?

Note that autodoc works similarly to Breathe in that it ultimately just generates Python domain directives.

On a positive note, these changes have alerted me about documented param names that are inconsistent with the actual param names in the signature 👍🏼

.. py:function:: some_func(param_name)

    :param incorrect_param_name: blah blah blah

This is mostly because I keep the majority of my docstrings - though not entire doctrings - in rst files (separate from py src code files) to save on storage space in microcontrollers.

You might consider using some sort of preprocessing step to strip docstrings --- not sure if there is already something to do that.

@2bndy5
Copy link
Collaborator

2bndy5 commented Apr 29, 2022

Still it is kind of odd that it would make a difference --- seems like autodoc or the Python domain may be treating the first parameter (i.e. the "self" parameter) specially.

automethod expects a self arg, so when there is only 1 arg it isn't found. Using autofunction works as expected for module-level function.

Can you reproduce the issue using the plain Python domain, rather than autodoc?

Not without intentionally omitting the arg from the signature. I probably shouldn't have mentioned it because it is not really a problem with these changes. Rather, it is a problem with improperly using autodoc. I only made note of it because it was but 1 of the obstacles I was working through in testing.

Currently I am assigning parameters the full name: .. That works fine for functions since they can't have any other "child" symbols.

I don't know what to suggest here either. Would there be a way to conditionally inject __init__ if the parent is a class?

So many people use autodoc that it would be a nuisance to require them to avoid using autoclass or configuring autodoc to not include __init__ members be default. The only reason I can get behind documenting __init__ separately is if I'm documenting a c-extension in which a class' c'tor is overloaded.

@jbms jbms force-pushed the feat-python-parameter-xrefs branch from a50e000 to f06059f Compare April 29, 2022 19:13
@jbms
Copy link
Owner Author

jbms commented Apr 29, 2022

Your suggestion of adding __init__ to the identifiers was excellent --- that is now done, which hopefully fixes the issue you identified.

2bndy5
2bndy5 previously approved these changes Apr 29, 2022
Copy link
Collaborator

@2bndy5 2bndy5 left a comment

Choose a reason for hiding this comment

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

Awesome. This is working flawlessly now. Thanks!

@2bndy5
Copy link
Collaborator

2bndy5 commented Apr 29, 2022

wait! now I can't get generate_synopses=None to work. I'm trying the config:

object_description_options = [
    ("py:.*", dict(generate_synopses=None)),
    ("py:parameter", dict(generate_synopses=None)),
]

EDIT: I flushed the docs/_build and re-built from scratch. Now the synopses are disabled as desired.

@2bndy5
Copy link
Collaborator

2bndy5 commented Apr 29, 2022

I'm also noticing that type hinting doesn't show if the signature doesn't have the type hints. This concerns me because I can't use proper type hinting in py src code's signatures on the platform I'm supporting (CircuitPython has no typing module).

For Example:

def func(arg1, arg2: int) -> None:
    """
    :param bool arg1: blah blah blah
    :param int arg2: blah blah blah
    """
    return None

Renders only the type hints given to the signature (not the type hints given to the param role).

@2bndy5 2bndy5 dismissed their stale review April 29, 2022 21:06

found quiet problems (no warning/errors)

@jbms
Copy link
Owner Author

jbms commented Apr 30, 2022

I'm also noticing that type hinting doesn't show if the signature doesn't have the type hints. This concerns me because I can't use proper type hinting in py src code's signatures on the platform I'm supporting (CircuitPython has no typing module).

This is now fixed --- if a parameter type is specified in the body, then it won't be overridden by the signature.

@jbms jbms force-pushed the feat-python-parameter-xrefs branch from f06059f to 506c988 Compare April 30, 2022 01:45
This adds a new `py:param` Sphinx role that can be used (in addition
to the `py:obj` role) to reference parameters.
@jbms jbms force-pushed the feat-python-parameter-xrefs branch from 506c988 to 23a1697 Compare April 30, 2022 02:21
@jbms jbms force-pushed the feat-python-parameter-xrefs branch from 23a1697 to b992486 Compare April 30, 2022 02:23
Copy link
Collaborator

@2bndy5 2bndy5 left a comment

Choose a reason for hiding this comment

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

I can't find anything else that sticks out. Thanks again!

@jbms jbms merged commit 75f0174 into main Apr 30, 2022
@jbms jbms deleted the feat-python-parameter-xrefs branch April 30, 2022 05:17
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.

2 participants