-
Notifications
You must be signed in to change notification settings - Fork 17
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
Validate docstring examples #364
Validate docstring examples #364
Conversation
68ff280
to
aa1debf
Compare
aa1debf
to
dc3c3bf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few comments, but amazing if now the examples are being tested and all really fixed!
If you really did a research and there are no tools available doing this, I'm thinking we might just create a repo for the tool, it looks like something that could be reused by other people and that the ecosystem is missing. But I'm also fine to add it here and split it in the future.
Also, please remove the "fixes #..." because we are still missing the testing of >>>
blocks, right? Or split the issue in 2.
and method_obj.__module__ == module.__name__ | ||
): | ||
if method_obj.__doc__ is not None: | ||
docs.append(method_obj.__doc__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this doesn't catch class attributes or class attributes? Does it catch properties
? It also doesn't catch module-level variables.
Any reason not to basically look for __doc__
in any symbol (as long as the symbol is originated in this module)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I learned only a few symbols have the doc property, namely modules, functions, methods and classes.
Well, that's not exactly correct, vars have the property, but it contains type documentation rather than user documentation.
E.g.
>>> x = 3
>>> dir(x)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
>>> x.__doc__
"int([x]) -> integer\nint(x, base=10) -> integer\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given. If x is a number, return x.__int__(). For floating point\nnumbers, this truncates towards zero.\n\nIf x is not a number or if base is given, then x must be a string,\nbytes, or bytearray instance representing an integer literal in the\ngiven base. The literal can be preceded by '+' or '-' and be surrounded\nby whitespace. The base defaults to 10. Valid bases are 0 and 2-36.\nBase 0 means to interpret the base from the string as an integer literal.\n>>> int('0b100', base=0)\n4"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, interesting. Well, the docs extracting tool (mkdocstrings) is considering strings following a variable a docstring, I guess it does it by parsing the Python file then. See for example:
- https://frequenz-floss.github.io/frequenz-sdk-python/v0.20/reference/frequenz/sdk/timeseries/#frequenz.sdk.timeseries.UNIX_EPOCH
frequenz-sdk-python/src/frequenz/sdk/timeseries/_base_types.py
Lines 13 to 14 in d3528e9
UNIX_EPOCH = datetime.fromtimestamp(0.0, tz=timezone.utc) """The UNIX epoch (in UTC)."""
I guess for the first version it is OK to ignore them, but we should support it in the future.
This also makes me think that testing the documentation should probably be part of building the documentation (or an option), so maybe in the future we could plug it to mkdocstring (making it a plugin or something). This will also ensure that the documentation we get to extract examples is the same as the one that will be rendered. The guy doing mkdocstrings seems very nice, he even replied to some issue in our repo when discussing about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is it with sybil? I guess it also not considering strings after a variable declaration a docstring for the variable, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Marenz ping, not sure if you could look into this. Is not super high prio, because I think it is much less likely to have examples here, but still it would be good to know. If it's not supported we can make an extra effort to try to avoid examples in these cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it is supported, as Sybil doesn't use the __doc__
property but parses the files itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you confirm this? I think it will be important to decide if we allow writing examples in variable documentation or not.
dc3c3bf
to
cd7008a
Compare
Signed-off-by: Mathias L. Baumann <mathias.baumann@frequenz.com>
Signed-off-by: Mathias L. Baumann <mathias.baumann@frequenz.com>
cd7008a
to
e4356e2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is amazing. I love the idea of making examples run in the same import context as the file they live in!
What I don't like at all is that this is not really a pytest
plugin, it is just hijacking the configuration file!
There is zero reason to run this in the context of pytest
, right? You are not using any pytest
facilities or features, right?
If so, just create a separate file. You don't even have to make it executable for now, we can just call it using python -m lint-code-examples
or whatever.
I think this should be definitely a separate tool, so IMHO the next step would be to move it into its own repo.
I liked that it re-uses the existing interface and error reporting |
Oh, really? Can you show an example output? I thought the output was just whatever |
|
And here an example with a failure:
|
I was kind of avoiding to generate actual files and tried to keep it all in memory, most for performance reasons. |
Recreated under my new username at #384 |
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.10.0 to 3.11.1. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/pytest-dev/pytest-mock/releases">pytest-mock's releases</a>.</em></p> <blockquote> <h2>v3.11.1</h2> <ul> <li> <p>Fixed introspection for failed <code>assert_has_calls</code> (<a href="https://github.com/pytest-dev/pytest-mock/issues/365">#365</a>).</p> </li> <li> <p>Updated type annotations for <code>mocker.patch</code> and <code>mocker.spy</code> (<a href="https://github.com/pytest-dev/pytest-mock/issues/364">#364</a>).</p> </li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst">pytest-mock's changelog</a>.</em></p> <blockquote> <h2>3.11.1 (2023-06-15)</h2> <ul> <li> <p>Fixed introspection for failed <code>assert_has_calls</code> (<code>[#365](https://github.com/pytest-dev/pytest-mock/issues/365)</code>_).</p> </li> <li> <p>Updated type annotations for <code>mocker.patch</code> and <code>mocker.spy</code> (<code>[#364](https://github.com/pytest-dev/pytest-mock/issues/364)</code>_).</p> </li> </ul> <p>.. _<a href="https://github.com/pytest-dev/pytest-mock/issues/365">#365</a>: <a href="https://github.com/pytest-dev/pytest-mock/pull/365">pytest-dev/pytest-mock#365</a> .. _<a href="https://github.com/pytest-dev/pytest-mock/issues/364">#364</a>: <a href="https://github.com/pytest-dev/pytest-mock/pull/364">pytest-dev/pytest-mock#364</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/d3e73f2e93f7b93eba0a36e17e43bafd969da4fe"><code>d3e73f2</code></a> Explicitly specify the tag name during GitHub Release</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/5668efe23e01673af9905febeefd9a9791b023f4"><code>5668efe</code></a> Update CHANGELOG for 3.11.0</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/c9a4d13ab4e808ef02250898a79c07b2acf76f61"><code>c9a4d13</code></a> Improve CI</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/5818160717a16b7f8867d950ed60b9dc3349ec8d"><code>5818160</code></a> Add assert_has_calls_wrapper (<a href="https://github.com/pytest-dev/pytest-mock/issues/365">#365</a>)</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/355b5aae1a1a9b3f2c17b2a6bbfde9980967bceb"><code>355b5aa</code></a> Fix return type annotation for patch and spy (<a href="https://github.com/pytest-dev/pytest-mock/issues/364">#364</a>)</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/8ba681201488bec69f27f6e9e2a56988a01f0d1b"><code>8ba6812</code></a> [pre-commit.ci] pre-commit autoupdate (<a href="https://github.com/pytest-dev/pytest-mock/issues/362">#362</a>)</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/786eaaf416fed8204a8aebb29cbf71527228a9f6"><code>786eaaf</code></a> [pre-commit.ci] pre-commit autoupdate (<a href="https://github.com/pytest-dev/pytest-mock/issues/360">#360</a>)</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/1cb146af21ffd0b5be24703419cf4a192c4ec0ab"><code>1cb146a</code></a> Push tag manually</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/9608e9659b1f062bb09915f3ef1c538ca7cb5c5f"><code>9608e96</code></a> Update permissions for deploy workflow</li> <li><a href="https://github.com/pytest-dev/pytest-mock/commit/c778ee73bd516d465dca3512d099682ba32108fa"><code>c778ee7</code></a> Fix deployment workflow (<a href="https://github.com/pytest-dev/pytest-mock/issues/358">#358</a>)</li> <li>Additional commits viewable in <a href="https://github.com/pytest-dev/pytest-mock/compare/v3.10.0...v3.11.1">compare view</a></li> </ul> </details> <br /> [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pytest-mock&package-manager=pip&previous-version=3.10.0&new-version=3.11.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details>
fixes #81