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

TOC broken when selecting non-modules #65

Closed
ofek opened this issue Mar 28, 2020 · 21 comments
Closed

TOC broken when selecting non-modules #65

ofek opened this issue Mar 28, 2020 · 21 comments
Labels
bug Something isn't working

Comments

@ofek
Copy link
Contributor

ofek commented Mar 28, 2020

# API

-----

::: datadog_checks.base.AgentCheck
    selection:
      members:
        - gauge
        - count
        - monotonic_count
        - rate
        - histogram
        - historate
        - service_check
        - event

::: datadog_checks.base.stubs.aggregator.AggregatorStub
    selection:
      members:
        - assert_metric
        - assert_metric_has_tag
        - assert_metric_has_tag_prefix
        - assert_service_check
        - assert_event
        - assert_all_metrics_covered
        - assert_no_duplicate_metrics
        - assert_no_duplicate_service_checks
        - assert_no_duplicate_all
        - reset

::: datadog_checks.base.stubs.datadog_agent.DatadogAgentStub

Capture

@pawamoy
Copy link
Member

pawamoy commented Mar 29, 2020

Try this:

# API

-----

::: datadog_checks.base.AgentCheck
    rendering:
      show_root_heading: yes
    selection:
      members:
        - gauge
        - count
        - monotonic_count
        - rate
        - histogram
        - historate
        - service_check
        - event

::: datadog_checks.base.stubs.aggregator.AggregatorStub
    rendering:
      show_root_heading: yes
    selection:
      members:
        - assert_metric
        - assert_metric_has_tag
        - assert_metric_has_tag_prefix
        - assert_service_check
        - assert_event
        - assert_all_metrics_covered
        - assert_no_duplicate_metrics
        - assert_no_duplicate_service_checks
        - assert_no_duplicate_all
        - reset

::: datadog_checks.base.stubs.datadog_agent.DatadogAgentStub
    rendering:
      show_root_heading: yes

Of course if you are writing your API docs like this everwhere, you can set this as default in mkdocs.yml:

plugins:
  - mkdocstrings:
      handlers:
        python:
          rendering:
            show_root_heading: yes

Here's how the template works:

  • if show_root_heading is true, show the top object heading
  • if show_root_heading is false, you still have a chance to add the object to the ToC with show_root_toc_entry set to true. Adding the ToC entry is required for cross-references to this object to work.

However I agree that the ToC seems broken: it's because I used h6 for the hidden ToC entries. I think it would be best to use the specified heading level. I'll fix this.

All the options are here: https://pawamoy.github.io/mkdocstrings/reference/handlers/python/#mkdocstrings.handlers.python.PythonRenderer.DEFAULT_CONFIG

@ofek
Copy link
Contributor Author

ofek commented Mar 29, 2020

This is amazing, everything looks so beautiful and heading_level gives us full control!!! We'll switch to your plugin hopefully today but there is one more critical issue I just noticed.

It seems like dynamically generated (at import time though) methods aren't detected:

https://github.com/DataDog/integrations-core/blob/hack-a-doc/datadog_checks_base/datadog_checks/base/utils/db/transform.py#L494-L517

::: datadog_checks.base.utils.db.transform.ColumnTransformers

::: datadog_checks.base.utils.db.transform.ExtraTransformers

Same for simple properties:

https://github.com/DataDog/integrations-core/blob/hack-a-doc/datadog_checks_base/datadog_checks/base/utils/http.py#L489-L496

::: datadog_checks.base.utils.http.StandardFields

The root header shows up of course, but no members at all. Am I missing an option?

@pawamoy
Copy link
Member

pawamoy commented Mar 29, 2020

Hmmm interesting. Indeed I never tried with dynamically added things. I'll play a bit with your example to see why mkdocstrings (pytkdocs really) does not detect the dynamically set members.

@ofek
Copy link
Contributor Author

ofek commented Mar 29, 2020

Hmm, pytkdocs seems to get them:

Capture

@ofek
Copy link
Contributor Author

ofek commented Mar 29, 2020

Ah, found it.

leaf is correct here and is the fully loaded object:

https://github.com/pawamoy/pytkdocs/blob/8dfd0bbf2e73977fc1cc9528e62057f3764a5a07/src/pytkdocs/loader.py#L205

but then here:

https://github.com/pawamoy/pytkdocs/blob/8dfd0bbf2e73977fc1cc9528e62057f3764a5a07/src/pytkdocs/loader.py#L207

there are no attributes b/c we parse using the AST of the source code rather than inspecting leaf

@pawamoy
Copy link
Member

pawamoy commented Mar 29, 2020

This is what I was suspecting for attributes. But the dynamically added members you're missing are methods no? They should be recognized as such in the get_<object_type>_documentation methods of pytkdocs loader.

If not, and your dynamic members are indeed attributes, yes, Python does not allow to attach a docstring to a variable so we have to parse them using AST, therefore dynamic stuff won't work (code is never executed while parsing attributes and their docstrings).

@ofek
Copy link
Contributor Author

ofek commented Mar 29, 2020

Yup basically we want to document Foo:

def a():
    """method a"""

def b():
    """method b"""

class Foo(object):
    pass

setattr(Foo, 'a', a)
setattr(Foo, 'b', b)

@ofek
Copy link
Contributor Author

ofek commented Mar 29, 2020

Keep in mind though that leaf.obj is correct, can we not inspect it without AST?

@pawamoy
Copy link
Member

pawamoy commented Mar 29, 2020

When I write "attribute" I mean "variable", sorry if this wasn't clear :)

Since you're setting methods with setattr, they should be picked up while recursing on objects and their members, so I'm not sure what happens here.

@ofek
Copy link
Contributor Author

ofek commented Mar 29, 2020

As far as I can tell it comes down to you re-parsing source code vs simply inspecting the loaded object that is already in memory (leaf.obj)

@pawamoy
Copy link
Member

pawamoy commented Mar 30, 2020

OK I found why nothing appears. The attributes you set dynamically with setattr are not methods. They are instances of __TransformerDocumentionHelper.

When pytkdocs iterates over the ColumnTransformers or ExtraTransformers classes' members, it has no idea how to handle such objects: they are not classes nor methods.

An idea that popped to mind is that we could inspect the object to see if it has a __doc__ attribute. But then how would we categorize it? The object must be an instance of Module, Class, Method, Function or Attribute. Such an object doesn't fit in any of those 😕

Is there a reason you do not attach directly the transformer instead of creating an object just to store its __doc__?

for name, transformer in sorted(COLUMN_TRANSFORMERS.items()):
    setattr(ColumnTransformers, name, transformer)

@ofek
Copy link
Contributor Author

ofek commented Mar 30, 2020

I was trying to avoid displaying a signature so it appears like an Enum, but yeah I'll do your way for now. Though I can't get the other case to work.

DEFAULT_TIMEOUT = 10

STANDARD_FIELDS = {
    'auth_type': 'basic',
    'aws_host': None,
    'aws_region': None,
    'aws_service': None,
    'connect_timeout': None,
    'extra_headers': None,
    'headers': None,
    'kerberos_auth': None,
    'kerberos_cache': None,
    'kerberos_delegate': False,
    'kerberos_force_initiate': False,
    'kerberos_hostname': None,
    'kerberos_keytab': None,
    'kerberos_principal': None,
    'log_requests': False,
    'ntlm_domain': None,
    'password': None,
    'persist_connections': False,
    'proxy': None,
    'read_timeout': None,
    'skip_proxy': False,
    'tls_ca_cert': None,
    'tls_cert': None,
    'tls_ignore_warning': False,
    'tls_private_key': None,
    'tls_verify': True,
    'timeout': DEFAULT_TIMEOUT,
    'username': None,
}

class StandardFields(object):
    pass

for field, value in sorted(STANDARD_FIELDS.items()):
    setattr(StandardFields, field, value)

@StephenBrown2
Copy link
Contributor

Why are you setting the attributes that way, and not in the class itself?

DEFAULT_TIMEOUT = 10

class StandardFields(object):
    auth_type = 'basic'
    aws_host = None
    aws_region = None
    aws_service = None
    connect_timeout = None
    extra_headers = None
    headers = None
    kerberos_auth = None
    kerberos_cache = None
    kerberos_delegate = False
    kerberos_force_initiate = False
    kerberos_hostname = None
    kerberos_keytab = None
    kerberos_principal = None
    log_requests = False
    ntlm_domain = None
    password = None
    persist_connections = False
    proxy = None
    read_timeout = None
    skip_proxy = False
    tls_ca_cert = None
    tls_cert = None
    tls_ignore_warning = False
    tls_private_key = None
    tls_verify = True
    timeout = DEFAULT_TIMEOUT
    username = None

@ofek
Copy link
Contributor Author

ofek commented Mar 31, 2020

I need to iterate through all the defined attributes

@ofek
Copy link
Contributor Author

ofek commented Mar 31, 2020

@pawamoy Also, just at a high level, I think we should support the idea that anything can happen at load time, same as other tools like the extension we're coming from and Sphinx. Can you explain what exactly we need to use the ast module for?

@pawamoy
Copy link
Member

pawamoy commented Mar 31, 2020

I think we should support the idea that anything can happen at load time, same as other tools like the extension we're coming from and Sphinx

It's already supported. We do import the code, so module scoped code is executed, and we can inspect it.

Can you explain what exactly we need to use the ast module for?

To get docstrings for attributes. Python has no way of attaching a docstring to a variable, so we must parse the code to get these pairs.

@pawamoy pawamoy added bug Something isn't working templates:python/material labels Mar 31, 2020
@ofek
Copy link
Contributor Author

ofek commented Apr 1, 2020

FYI I just tried @StephenBrown2's way, and that also does not work

@ofek
Copy link
Contributor Author

ofek commented Apr 1, 2020

nor does stdlib's Enum

@ofek
Copy link
Contributor Author

ofek commented Apr 1, 2020

Got it!

class StandardFields(object):
    pass

StandardFields.__doc__ = '\n'.join('- `{}`'.format(field) for field in STANDARD_FIELDS)

@pawamoy
Copy link
Member

pawamoy commented Apr 1, 2020

Nice!

The things you tried didn't work because pytkdocs only collects attributes with docstrings. There's an issue for this on pytkdocs repo: mkdocstrings/pytkdocs#11, feel free to comment or vote!

@pawamoy pawamoy closed this as completed in 475cc62 Apr 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants