ruff/crates/ty_python_semantic/resources/mdtest/import/relative.md
Aria Desires edeb45804e
Some checks are pending
CI / cargo build (release) (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
[ty] fallback to resolve_real_module in file_to_module (#20461)
This is a naive(?) implementation of the approach @MichaReiser
originally suggested to me in https://github.com/astral-sh/ty/issues/869

Fixes https://github.com/astral-sh/ty/issues/869
Fixes https://github.com/astral-sh/ty/issues/1195
2025-09-24 21:15:35 -04:00

4.2 KiB

Relative

Non-existent

package/__init__.py:

package/bar.py:

from .foo import X  # error: [unresolved-import]

reveal_type(X)  # revealed: Unknown

Simple

package/__init__.py:

package/foo.py:

X: int = 42

package/bar.py:

from .foo import X

reveal_type(X)  # revealed: int

Simple With Stub and Implementation

This is a regression test for an issue with relative imports in implementation files when a stub is also defined.

package/__init__.py:

package/foo.py:

X: int = 42

package/bar.py:

from .foo import X

reveal_type(X)  # revealed: int

package/bar.pyi:

from .foo import X

reveal_type(X)  # revealed: int

Dotted

package/__init__.py:

package/foo/bar/baz.py:

X: int = 42

package/bar.py:

from .foo.bar.baz import X

reveal_type(X)  # revealed: int

Bare to package

package/__init__.py:

X: int = 42

package/bar.py:

from . import X

reveal_type(X)  # revealed: int

Non-existent + bare to package

package/bar.py:

from . import X  # error: [unresolved-import]

reveal_type(X)  # revealed: Unknown

Dunder init

package/__init__.py:

from .foo import X

reveal_type(X)  # revealed: int

package/foo.py:

X: int = 42

Non-existent + dunder init

package/__init__.py:

from .foo import X  # error: [unresolved-import]

reveal_type(X)  # revealed: Unknown

Long relative import

package/__init__.py:

package/foo.py:

X: int = 42

package/subpackage/subsubpackage/bar.py:

from ...foo import X

reveal_type(X)  # revealed: int

Unbound symbol

package/__init__.py:

package/foo.py:

x  # error: [unresolved-reference]

package/bar.py:

from .foo import x  # error: [unresolved-import]

reveal_type(x)  # revealed: Unknown

Bare to module

package/__init__.py:

package/foo.py:

X: int = 42

package/bar.py:

from . import foo

reveal_type(foo.X)  # revealed: int

Non-existent + bare to module

This test verifies that we emit an error when we try to import a symbol that is neither a submodule nor an attribute of package.

package/__init__.py:

package/bar.py:

from . import foo  # error: [unresolved-import]

reveal_type(foo)  # revealed: Unknown

Import submodule from self

We don't currently consider from...import statements when building up the imported_modules set in the semantic index. When accessing an attribute of a module, we only consider it a potential submodule when that submodule name appears in the imported_modules set. That means that submodules that are imported via from...import are not visible to our type inference if you also access that submodule via the attribute on its parent package.

package/__init__.py:

package/foo.py:

X: int = 42

package/bar.py:

from . import foo
import package

# error: [unresolved-attribute] "Type `<module 'package'>` has no attribute `foo`"
reveal_type(package.foo.X)  # revealed: Unknown

Relative imports at the top of a search path

Relative imports at the top of a search path result in a runtime error: ImportError: attempted relative import with no known parent package. That's why ty should disallow them.

parser.py:

X: int = 42

__main__.py:

from .parser import X  # error: [unresolved-import]

Relative imports in site-packages

Relative imports in site-packages are correctly resolved even when the site-packages search path is a subdirectory of the first-party search path. Note that mdtest sets the first-party search path to /src/, which is why the virtual environment in this test is a subdirectory of /src/, even though this is not how a typical Python project would be structured:

[environment]
python = "/src/.venv"
python-version = "3.13"

/src/bar.py:

from foo import A

reveal_type(A)  # revealed: <class 'A'>

/src/.venv/<path-to-site-packages>/foo/__init__.py:

from .a import A as A

/src/.venv/<path-to-site-packages>/foo/a.py:

class A: ...