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
[lint] run mypy on tests/test_config/test_config.py
#12198
base: master
Are you sure you want to change the base?
Conversation
config = Config({}, {'copyright': conf_copyright}) | ||
correct_copyright_year(_app=None, config=config) | ||
correct_copyright_year(..., config=config) # type: ignore[arg-type] |
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.
Suggetion: we could try to unpick this a bit and make the interface, and then test case, cleaner. Perhaps correct_copyright_year
should only accept a single argument, the app to check/correct copyright for?
yes, I would certainly suggest enforcing types less strictly in tests, since obviously these are only for our internal use rather than anything exposed to the public, and we don't want to making writing test to much of a burden 😅 |
i think adding and assuming the existence of properties on a foreign config object is a pattern which is fundamentally opposed to strict type checking. i presume adding A better mechanism (though a much larger architectural change) might be for plugins to somehow subclass |
This one is feasible, but there is an issue with the fact that configuration objects behave as If you want, it's like a |
Yeh i think these are beyond the scope of this PR, Then I have a layer on top of sphinx to:
I'd be intersting if any of this, at least conceptually, could be upstreamed to sphinx core |
From what I understood, extensions would write a dataclass as their "additional configuration" and you would merge them together right, producing a "single" configuration class that inherits from those dataclasses? While this logic could work, since Python does not have an intersection type (unlike TypeScript), mypy would not be able to tell that the configuration object after adding multiple subconfigurations dataclasses supports all those fields (unless we do a plugin I think and fake an inheritance). Currently, in Sphinx, the simple fact that you can add configuration values however you see fit makes Config objects impossible (from a static point of view) to lint them. |
yep, something like that 😅
Exactly; although the code I have set up in myst-parser works for me, and I'm pretty happy with it, I'm certainly not claiming it is "strictly correct" (if that it is even possible) |
this is conceptually similar to what i do in sphinx-graph (which itself is a much, much leaner take on the sphinx-needs use-case) (in this case i'm reading from the environment rather than config) class State:
"""State object for Sphinx Graph vertices."""
def __init__(
self,
vertices: dict[str, tuple[int, Info]],
graph: rx.PyDiGraph[str, str | None],
) -> None:
"""Create a new state object."""
self._vertices = vertices
self._graph = graph
@classmethod
def read(cls, env: BuildEnvironment) -> State:
"""Read the State object for the given environment.
This is a read-only view of the state. Changes will not be saved.
"""
vertices = getattr(env, "graph_vertices", {})
graph: rx.PyDiGraph[str, str | None] = getattr(
env, "graph_graph", rx.PyDiGraph(multigraph=False)
)
return State(vertices, graph) it's 'loosely-typed', but i do it only once in the codebase and use a typed interface everywhere else |
sphinx-needs also does a similar thing, to return extension specific state from the environment 😄 at the end of the day, this seems probably the most pragmatic approach (confining the loose typing to a single place) I don't know if there is any generalisation to be had here, to help extension developers use the "best practices", or if you just leave them to their ad-hoc solutions 🤔 |
I think we should wait until we have intersection types in Python (see python/typing#213). After that, it will be easier to type extendable configurations. |
When implementing #12196, I wanted to use mypy on it. So I did that. However, I observed that we are facing some issues (that I didn't think of before...)
mock.patch
) areNonCallableMock
objects. However, typing them as such makes PyCharm lose it's autocompletion... On the other hand, typing them asAny
makemypy
lose its purpose... (and note thatNonCallableMock
inherits fromAny
in typeshed).Config
object does not support__setattr__
explicitly, although it should, making mypy raise a lot ofattr-defined
errors. This only happens if you type app asSphinx
. So... I'm not really sure we are gaining anything in the future if we need to either add an explicit__setattr__
or if we need to add# type: ignore
everywhere...I think we need to delay #12097 until we have a good idea on how to minimize the introduction of(plugin wouldn't solve the issue).# type: ignore
. The only good idea I can come up with, without destroying both autocompletion and mypy at the same time, is to implement a mypy plugin which would allow us bypassing type annotations at the level of the test itself.I had the habit to ask people to either not type or type everything in the function. However, with the limitations by mypy and IDEs, I feel we need to allow incomplete definitions (for tests at least).
cc @danieleades @chrisjsewell @jayaddison