mirror of https://github.com/astral-sh/ruff
210 lines
8.3 KiB
Plaintext
210 lines
8.3 KiB
Plaintext
// This is a Dot representation of a flow diagram meant to describe Python's
|
|
// import resolution rules. This particular diagram starts with one particular
|
|
// search path and one particular module name. (Typical import resolution
|
|
// implementation will try multiple search paths.)
|
|
//
|
|
// This diagram also assumes that stubs are allowed. The ty implementation
|
|
// of import resolution makes this a configurable parameter, but it should
|
|
// be straight-forward to adapt this flow diagram to one where no stubs
|
|
// are allowed. (i.e., Remove `.pyi` checks and remove the `package-stubs`
|
|
// handling.)
|
|
//
|
|
// This flow diagram exists to act as a sort of specification. At the time
|
|
// of writing (2025-07-29), it was written to capture the implementation of
|
|
// resolving a *particular* module name. We wanted to add another code path for
|
|
// *listing* available module names. Since code reuse is somewhat difficult
|
|
// between these two access patterns, I wrote this flow diagram as a way of 1)
|
|
// learning how module resolution works and 2) to provide a "source of truth"
|
|
// that we can compare implementations to.
|
|
//
|
|
// To convert this file into an actual image, you'll need the `dot` program
|
|
// (which is typically part of a `graphviz` package in a Linux distro):
|
|
//
|
|
// dot -Tsvg import-resolution-diagram.dot > import-resolution-diagram.svg
|
|
//
|
|
// And then view it in a web browser (or some other svg viewer):
|
|
//
|
|
// firefox ./import-resolution-diagram.svg
|
|
//
|
|
// [Dot]: https://graphviz.org/doc/info/lang.html
|
|
|
|
digraph python_import_resolution {
|
|
labelloc="t";
|
|
label=<
|
|
<b>Python import resolution flow diagram for a single module name in a single "search path"</b>
|
|
<br/>(assumes that the module name is valid and that stubs are allowed)
|
|
>;
|
|
|
|
// These are the final affirmative states we can end up in. A
|
|
// module is a regular `foo.py` file module. A package is a
|
|
// directory containing an `__init__.py`. A namespace package is a
|
|
// directory that does *not* contain an `__init__.py`.
|
|
module [label="Single-file Module",peripheries=2];
|
|
package [label="Package",peripheries=2];
|
|
namespace_package [label="Namespace Package",peripheries=2];
|
|
not_found [label="Module Not Found",peripheries=2];
|
|
|
|
// The final states are wrapped in a subgraph with invisible edges
|
|
// to convince GraphViz to give a more human digestible rendering.
|
|
// Without this, the nodes are scattered every which way and the
|
|
// flow diagram is pretty hard to follow. This encourages (but does
|
|
// not guarantee) GraphViz to put these nodes "close" together, and
|
|
// this generally gets us something grokable.
|
|
subgraph final {
|
|
rank = same;
|
|
module -> package -> namespace_package -> not_found [style=invis];
|
|
}
|
|
|
|
START [label=<<b>START</b>>];
|
|
START -> non_shadowable;
|
|
|
|
non_shadowable [label=<
|
|
Is the search path not the standard library and<br/>
|
|
the module name is `types` or some other built-in?
|
|
>];
|
|
non_shadowable -> not_found [label="Yes"];
|
|
non_shadowable -> stub_package_check [label="No"];
|
|
|
|
stub_package_check [label=<
|
|
Is the search path in the standard library?
|
|
>];
|
|
stub_package_check -> stub_package_set [label="No"];
|
|
stub_package_check -> determine_parent_kind [label="Yes"];
|
|
|
|
stub_package_set [label=<
|
|
Set `module_name` to `{top-package}-stubs.{rest}`
|
|
>];
|
|
stub_package_set -> determine_parent_kind;
|
|
|
|
determine_parent_kind [label=<
|
|
Does every parent package of `module_name`<br/>
|
|
correspond to a directory that contains an<br/>
|
|
`__init__.py` or an `__init__.pyi`?
|
|
>];
|
|
determine_parent_kind -> regular_parent_std [label="Yes"];
|
|
determine_parent_kind -> namespace_parent_regular_check [label="No"];
|
|
|
|
regular_parent_std [label=<
|
|
Does the search path correspond to the standard library?
|
|
>];
|
|
regular_parent_std -> resolved_parent_package [label="No"];
|
|
regular_parent_std -> regular_parent_typeshed_check [label="Yes"];
|
|
|
|
regular_parent_typeshed_check [label=<
|
|
Does every parent package of<br/>
|
|
`module_name` exist on the configured<br/>
|
|
Python version according to <br/>
|
|
typeshed's VERSIONS file?
|
|
>];
|
|
regular_parent_typeshed_check -> resolved_parent_package [label="Yes"];
|
|
regular_parent_typeshed_check -> bail [label="No"];
|
|
|
|
namespace_parent_regular_check [label=<
|
|
Is the direct parent package<br/>
|
|
a directory that contains<br/>
|
|
an `__init__.py` or `__init__.pyi`?
|
|
>];
|
|
namespace_parent_regular_check -> bail [label="Yes"];
|
|
namespace_parent_regular_check -> namespace_parent_std [label="No"];
|
|
|
|
namespace_parent_std [label=<
|
|
Does the search path correspond to the standard library?
|
|
>];
|
|
namespace_parent_std -> namespace_parent_module_check [label="No"];
|
|
namespace_parent_std -> namespace_parent_typeshed_check [label="Yes"];
|
|
|
|
namespace_parent_typeshed_check [label=<
|
|
Does the direct parent package of<br/>
|
|
`module_name` exist on the configured<br/>
|
|
Python version according to <br/>
|
|
typeshed's VERSIONS file?
|
|
>];
|
|
namespace_parent_typeshed_check -> namespace_parent_module_check [label="Yes"];
|
|
namespace_parent_typeshed_check -> bail [label="No"];
|
|
|
|
namespace_parent_module_check [label=<
|
|
Does the direct parent package<br/>
|
|
have a sibling file with the same<br/>
|
|
basename and a `py` or `pyi` extension?<br/>
|
|
>];
|
|
namespace_parent_module_check -> bail [label="Yes"];
|
|
namespace_parent_module_check -> namespace_parent_above [label="No"];
|
|
|
|
namespace_parent_above [label=<
|
|
Is every parent above the direct<br/>
|
|
parent package a normal package or<br/>
|
|
otherwise satisfy the previous two<br/>
|
|
namespace package requirements?
|
|
>];
|
|
namespace_parent_above -> bail [label="No"];
|
|
namespace_parent_above -> resolved_parent_package [label="Yes"];
|
|
|
|
resolved_parent_package [label=<
|
|
After replacing `.` with `/` in module name,<br/>
|
|
does `{path}/__init__.py` or `{path}/__init__.pyi` exist?
|
|
>];
|
|
resolved_parent_package -> package [label="Yes"];
|
|
resolved_parent_package -> maybe_module [label="No"];
|
|
|
|
maybe_module [label=<
|
|
Does `{path}.py` or `{path}.pyi` exist?
|
|
>];
|
|
maybe_module -> maybe_module_std [label="Yes"];
|
|
maybe_module -> maybe_namespace [label="No"];
|
|
|
|
maybe_module_std [label=<
|
|
Does the search path correspond to the standard library?
|
|
>];
|
|
maybe_module_std -> module [label="No"];
|
|
maybe_module_std -> maybe_module_typeshed_check [label="Yes"];
|
|
|
|
maybe_module_typeshed_check [label=<
|
|
Does the module corresponding to `{path}`<br/>
|
|
exist on the configured<br/>
|
|
Python version according to <br/>
|
|
typeshed's VERSIONS file?
|
|
>];
|
|
maybe_module_typeshed_check -> module [label="Yes"];
|
|
maybe_module_typeshed_check -> maybe_namespace [label="No"];
|
|
|
|
// N.B. In the actual implementation, this check is
|
|
// only done when the search path *isn't* the standard
|
|
// library. That's because typeshed doesn't use namespace
|
|
// packages, so this (and the typeshed VERSIONS check)
|
|
// can all be skipped as an optimization. But the flow
|
|
// diagram still represents this because this could in
|
|
// theory change and optimizations really should be the
|
|
// domain of the implementation, not the spec.
|
|
maybe_namespace [label=<
|
|
Is `{path}` a directory?
|
|
>];
|
|
maybe_namespace -> maybe_namespace_std [label="Yes"];
|
|
maybe_namespace -> bail [label="No"];
|
|
|
|
maybe_namespace_std [label=<
|
|
Does the search path correspond to the standard library?
|
|
>];
|
|
maybe_namespace_std -> namespace_package [label="No"];
|
|
maybe_namespace_std -> maybe_namespace_typeshed_check [label="Yes"];
|
|
|
|
maybe_namespace_typeshed_check [label=<
|
|
Does the module corresponding to `{path}`<br/>
|
|
exist on the configured<br/>
|
|
Python version according to <br/>
|
|
typeshed's VERSIONS file?
|
|
>];
|
|
maybe_namespace_typeshed_check -> namespace_package [label="Yes"];
|
|
maybe_namespace_typeshed_check -> bail [label="No"];
|
|
|
|
bail [label=<
|
|
Is `module_name` set to a stub package candidate?
|
|
>];
|
|
bail -> not_found [label="No"];
|
|
bail -> retry [label="Yes"];
|
|
|
|
retry [label=<
|
|
Reset `module_name` to original
|
|
>];
|
|
retry -> determine_parent_kind;
|
|
}
|