-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
DOC: Enable nitpicky #14768
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
DOC: Enable nitpicky #14768
Conversation
a61c555
to
528c47f
Compare
@anntzer I've added a location string to the JSON file – its not perfect (it replaces paths not within matplotlib with Unfortunately, |
FilledArrow's docstring is
so there's no markup problems there? |
|
Either way is fine, it's not very big and we'd not be exposing it as public API anyways.
Ah, I missed that non-listed entries would be subjected to nitpicking, that's good.
and then one can just delete them manually from the json file.
Grepping for FilledArrow in the codebase yields
so it's not clear what could be done to fix that entry, for example. Just to be clear, I think this would be a nice feature, I'm just trying to make sure we can use it efficiently :) |
As someone who is prone to having malformed references in the docs I'd be happy t see this merged. Is it ready for review? |
I am working on two last small pieces –
|
@alexrudy Thanks for working on this. I've marked this as "work in progress" so that reviewers don't spend extra time on this until it's ready. Please report back when you think this is ready for review. |
I think I'm ready for some more eyes on this! Where we stand: @anntzer had a good request – record where warnings came from. That required a bit more work inside the extension, but now we get two things:
@timhoffm – I can't add or remove labels, but removing the WIP label seems appropriate now. |
This is definitely much better now :) looking at missing-references.json under e.g. the py:obj key, one can easily find fixable references. A few more requests though:
but https://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.angle_spectrum.html actually links correctly to numpy.angle_spectrum. Likewise for some stdlib references.
which likely(?) come from the fact that in https://matplotlib.org/devdocs/api/_as_gen/matplotlib.animation.AVConvFileWriter.html, under the Methods section, autosummary tries to link to AVConvFileWriter but that should go to the parent class' doc, https://matplotlib.org/devdocs/api/_as_gen/matplotlib.animation.MovieWriter.html#matplotlib.animation.MovieWriter.bin_path; not sure whether this should be filed as a sphinx bug?
|
Hmm – @anntzer I think you have identified something more fundamental. I wonder if some of these references are being resolved by the sphinx |
Alright, one more attempt at this! Since last time:
Two major outcomes from this:
If I/we ever want to break this extension out into its own repo, I would want to ensure that upstream sphinx adds better support for this kind of thing. |
doc/sphinxext/missing_references.py
Outdated
path = os.path.relpath(path, start=basepath) | ||
|
||
if path.startswith(os.path.pardir): | ||
path = os.path.join("<external>", os.path.basename(path)) |
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 think this will give a different result if someone builds on Windows, which we actually don't want? So force the use of "/" here? (I would use pathlib.Path.as_posix because pathlib is nice :), but you don't have to.)
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.
Oh, good catch! I'll switch to pathlib
, I like that too.
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 wanted to go with pathlib, but I decided against it, because the semantics of Path.relative_to
aren't the same as os.path.relpath
(the former raises an error for paths which can't be constructed as children of the base directory) – so it was easier just to pull out posixpath
.
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.
- if you are relying on relpath having different semantics from Path.relative_to (haven't looked in depth) you should leave a comment so that the code doesn't "helpfully" get rewritten to use relative_to in the future...
- otoh, you know you can pass pathlib Paths to os.path.relpath? alternatively, something like
if basepath in path.parents: ...
- I guess if you do use pathlib then
PurePosixPath
is the way to go to have forward slashes all the time, as you're not going to do any filesystem ops on them.
Nice! Just one review point left I think, I'm going to skip style nitpicks unless you want to hear them :) |
Always happy to hear any nitpicks, @anntzer ! |
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.
none of these style comments should be considered too seriously :)
doc/sphinxext/missing_references.py
Outdated
|
||
if path: | ||
|
||
basepath = os.path.abspath(os.path.join(app.confdir, "..")) |
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.
actually I think this will fail if building the docs against a non-editable install? (so that you'd find the wrong matplotlib by going just up once from doc/conf.py) Admittedly a bit theoretical [though note that conda or linux packagers have been known to do stranger stuff], but you could possibly instead normalize the paths relative to Path(matplotlib.__init__.__file__).parent.parent
(which helpfully also works for mpl-toolkits) instead (I guess if this was to be a mpl-independent extension you'd need to make this a config option, but note that in such a case your current approach also fails if conf.py is under doc/source/conf.py rather than doc/conf.py, which is a valid sphinx layout).
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.
Ahh – yes, this makes sense. A very good catch.
I'm going to test your suggestion and see what happens. It definitely won't work if this becomes a true sphinx extension, but there are are a few ways around that in the future.
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've tried some things out here –
It's quite difficult to gracefully handle building the docs correctly when matplotlib
isn't an editable install. Figuring out what is a "matplotlib" path vs any other module, and producing paths which match the editable install case, is very difficult. e.g. in the source tree mode, we have a path lib/matplotlib/axes/_axes.py
, but in installed mode, this is something like site-packages/matplotlib/axes/_axes.py
. Although we could normalize those two paths, it is a lot of work for a particular edge case, for functionality that isn't very important to this extension. (The features of this extension are already ballooning beyond what I had envisioned).
Instead, I propose that we detect when the matplotlib source directory doesn't seem to be in the right place relative to the docs directory and in those cases, we don't worry about warning for unused ignores. Everything else should still work fine in those cases – the actual process of ignoring unmatched references doesn't use the file paths at all.
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.
Sorry to have sent you on a wild goose chase... just ignoring warnings if not in a standard layout is perfectly fine.
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.
No worries – it was interesting idea to try. I'm just trying to balance making things useful with making the perfect sphinx extension!
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.
Some typos and other clarifications.
self.app = app | ||
super().__init__() | ||
|
||
def _record_reference(self, record): |
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.
Not sure why you need this separate method when it only has one caller, which is also a method on the same class.
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.
Its done as a convenience – the filter() method must return True
or False
, but the _record_reference
method doesn't actually do any log filtering (all messages should get logged) so its a bit clearer IMO to return True
from the filter method and put the logic somewhere else (so that we don't have to ensure that early returns from _record_reference
also return True
).
doc/sphinxext/missing_references.py
Outdated
missing_reference_filter = MissingReferenceFilter(app) | ||
for handler in sphinx_logger.handlers[:]: | ||
if (isinstance(handler, sphinx_logging.WarningStreamHandler) and | ||
missing_reference_filter not in handler.filters): |
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.
Word-wrapped if
should be indented one more level.
184d938
to
74af5b6
Compare
for ignored_reference_location in locations: | ||
if (ignored_reference_location not in | ||
missing_reference_locations): | ||
msg = (f"Reference {domain_type} {target} for " |
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.
indent a bit weird
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.
Agreed – @QuLogic what were you going for with your earlier comment about this if statement?
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.
This indent is correct now; I don't know what @anntzer finds weird.
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.
Though I suppose I could point out that the if
condition could fit on one line.
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.
It's the f-string indent which is currently
msg = (f"Reference {domain_type} {target} for "
f"{ignored_reference_location} can be removed"
f" from {app.config.missing_references_filename}."
"It is no longer a missing reference in the docs.")
but should rather be something like
msg = (f"Reference {domain_type} {target} for "
f"{ignored_reference_location} can be removed"
f" from {app.config.missing_references_filename}."
"It is no longer a missing reference in the docs.")
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.
enough nitpicking :p
I'm going to rebase and fix the failing tests on this. |
66db761
to
2e270bd
Compare
2e270bd
to
0c725e2
Compare
0c725e2
to
6fab2e0
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.
Ok, let's give this a try. 😄
PR Summary
Enable Sphinx nitpicky mode, which raises a warning when a reference can't be found (and when combined with the
-W
option, causes an error when a reference can't be found).There are currently ~2500 references which can't be resolved. I can't fix all of them in this SciPy sprint 🙁 but I did write a sphinx extension,
missing_references
which:(a) records all of the missing references from one sphinx run in a JSON file.
(b) ignores those missing references on future runs.
This should mean that we can turn Sphinx nitpicky on for now, and it will emit a warning for new problems.
PR Checklist