ruff/crates/ty_python_semantic/resources/mdtest/import/relative.md
2025-05-03 19:49:15 +02:00

3.8 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

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: Literal[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: ...