diff --git a/Cargo.lock b/Cargo.lock index 930364639c4cf3..49689dfdd73d4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -930,35 +930,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - [[package]] name = "env_home" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -3046,16 +3023,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "ruff_python_resolver" -version = "0.0.0" -dependencies = [ - "env_logger", - "insta", - "log", - "tempfile", -] - [[package]] name = "ruff_python_semantic" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 32804e36ba13a8..b9de8e5209f944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,6 @@ dashmap = { version = "6.0.1" } dir-test = { version = "0.4.0" } dunce = { version = "1.0.5" } drop_bomb = { version = "0.1.5" } -env_logger = { version = "0.11.0" } etcetera = { version = "0.10.0" } fern = { version = "0.7.0" } filetime = { version = "0.2.23" } diff --git a/crates/ruff_python_resolver/Cargo.toml b/crates/ruff_python_resolver/Cargo.toml deleted file mode 100644 index 65d9847c9d7179..00000000000000 --- a/crates/ruff_python_resolver/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "ruff_python_resolver" -version = "0.0.0" -description = "A Python module resolver for Ruff" -publish = false -authors = { workspace = true } -edition = { workspace = true } -rust-version = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } -repository = { workspace = true } -license = { workspace = true } - -[lib] -doctest = false - -[dependencies] -log = { workspace = true } - -[dev-dependencies] -env_logger = { workspace = true } -tempfile = { workspace = true } -insta = { workspace = true } - -[lints] -workspace = true diff --git a/crates/ruff_python_resolver/resources/test/airflow/README.md b/crates/ruff_python_resolver/resources/test/airflow/README.md deleted file mode 100644 index 803b7c401b1d06..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# airflow - -This is a mock subset of the Airflow repository, used to test module resolution. diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/__init__.py deleted file mode 100644 index 8b137891791fe9..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/api/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/api/__init__.py deleted file mode 100644 index 8b137891791fe9..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/api/common/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/api/common/__init__.py deleted file mode 100644 index 8b137891791fe9..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/api/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/api/common/mark_tasks.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/api/common/mark_tasks.py deleted file mode 100644 index 9b13b3153ea0ab..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/api/common/mark_tasks.py +++ /dev/null @@ -1,14 +0,0 @@ -# Standard library. -import os - -# First-party. -from airflow.jobs.scheduler_job_runner import SchedulerJobRunner - -# Stub file. -from airflow.compat.functools import cached_property - -# Namespace package. -from airflow.providers.google.cloud.hooks.gcs import GCSHook - -# Third-party. -from sqlalchemy.orm import Query diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/__init__.py deleted file mode 100644 index 13a83393a9124b..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/functools.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/functools.py deleted file mode 100644 index 75a6dead35cac2..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/functools.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty file included to support filesystem-based resolver tests.""" diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/functools.pyi b/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/functools.pyi deleted file mode 100644 index 75a6dead35cac2..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/compat/functools.pyi +++ /dev/null @@ -1 +0,0 @@ -"""Empty file included to support filesystem-based resolver tests.""" diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/jobs/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/jobs/__init__.py deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/jobs/scheduler_job_runner.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/jobs/scheduler_job_runner.py deleted file mode 100644 index 75a6dead35cac2..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/jobs/scheduler_job_runner.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty file included to support filesystem-based resolver tests.""" diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/__init__.py deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/cloud/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/cloud/__init__.py deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/cloud/hooks/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/cloud/hooks/__init__.py deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/cloud/hooks/gcs.py b/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/cloud/hooks/gcs.py deleted file mode 100644 index 75a6dead35cac2..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/airflow/providers/google/cloud/hooks/gcs.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty file included to support filesystem-based resolver tests.""" diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/_watchdog_fsevents.cpython-311-darwin.so b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/_watchdog_fsevents.cpython-311-darwin.so deleted file mode 100644 index 0dff73800237f0..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/_watchdog_fsevents.cpython-311-darwin.so +++ /dev/null @@ -1 +0,0 @@ -# Empty file included to support filesystem-based resolver tests. diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/__init__.py deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/__init__.pyi b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/__init__.pyi deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so deleted file mode 100644 index 0dff73800237f0..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so +++ /dev/null @@ -1 +0,0 @@ -# Empty file included to support filesystem-based resolver tests. diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/py.typed b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/py.typed deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/__init__.py deleted file mode 100644 index 8b137891791fe9..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/__init__.py b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/__init__.py deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py deleted file mode 100644 index 75a6dead35cac2..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty file included to support filesystem-based resolver tests.""" diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py deleted file mode 100644 index 75a6dead35cac2..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty file included to support filesystem-based resolver tests.""" diff --git a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py b/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py deleted file mode 100644 index 75a6dead35cac2..00000000000000 --- a/crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty file included to support filesystem-based resolver tests.""" diff --git a/crates/ruff_python_resolver/src/config.rs b/crates/ruff_python_resolver/src/config.rs deleted file mode 100644 index 072e44a9935259..00000000000000 --- a/crates/ruff_python_resolver/src/config.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::path::PathBuf; - -pub(crate) struct Config { - /// Path to use for typeshed definitions. - pub(crate) typeshed_path: Option, - - /// Path to custom typings (stub) modules. - pub(crate) stub_path: Option, - - /// Path to a directory containing one or more virtual environment - /// directories. This is used in conjunction with the "venv" name in - /// the config file to identify the python environment used for resolving - /// third-party modules. - pub(crate) venv_path: Option, - - /// Default venv environment. - pub(crate) venv: Option, -} diff --git a/crates/ruff_python_resolver/src/execution_environment.rs b/crates/ruff_python_resolver/src/execution_environment.rs deleted file mode 100644 index b969ddc42b8eb0..00000000000000 --- a/crates/ruff_python_resolver/src/execution_environment.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::path::PathBuf; - -use crate::python_platform::PythonPlatform; -use crate::python_version::PythonVersion; - -#[derive(Debug)] -pub(crate) struct ExecutionEnvironment { - /// The root directory of the execution environment. - pub(crate) root: PathBuf, - - /// The Python version of the execution environment. - pub(crate) python_version: PythonVersion, - - /// The Python platform of the execution environment. - pub(crate) python_platform: PythonPlatform, - - /// The extra search paths of the execution environment. - pub(crate) extra_paths: Vec, -} diff --git a/crates/ruff_python_resolver/src/host.rs b/crates/ruff_python_resolver/src/host.rs deleted file mode 100644 index be9b0a5e601d80..00000000000000 --- a/crates/ruff_python_resolver/src/host.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Expose the host environment to the resolver. - -use std::path::PathBuf; - -use crate::python_platform::PythonPlatform; -use crate::python_version::PythonVersion; - -/// A trait to expose the host environment to the resolver. -pub(crate) trait Host { - /// The search paths to use when resolving Python modules. - fn python_search_paths(&self) -> Vec; - - /// The Python version to use when resolving Python modules. - fn python_version(&self) -> PythonVersion; - - /// The OS platform to use when resolving Python modules. - fn python_platform(&self) -> PythonPlatform; -} - -/// A host that exposes a fixed set of search paths. -pub(crate) struct StaticHost { - search_paths: Vec, -} - -impl StaticHost { - pub(crate) fn new(search_paths: Vec) -> Self { - Self { search_paths } - } -} - -impl Host for StaticHost { - fn python_search_paths(&self) -> Vec { - self.search_paths.clone() - } - - fn python_version(&self) -> PythonVersion { - PythonVersion::Py312 - } - - fn python_platform(&self) -> PythonPlatform { - PythonPlatform::Darwin - } -} diff --git a/crates/ruff_python_resolver/src/implicit_imports.rs b/crates/ruff_python_resolver/src/implicit_imports.rs deleted file mode 100644 index afa81d27ba8eff..00000000000000 --- a/crates/ruff_python_resolver/src/implicit_imports.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::collections::BTreeMap; -use std::ffi::OsStr; -use std::io; -use std::path::{Path, PathBuf}; - -use crate::{native_module, py_typed}; - -/// A map of the submodules that are present in a namespace package. -/// -/// Namespace packages lack an `__init__.py` file. So when resolving symbols from a namespace -/// package, the symbols must be present as submodules. This map contains the submodules that are -/// present in the namespace package, keyed by their module name. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub(crate) struct ImplicitImports(BTreeMap); - -impl ImplicitImports { - /// Find the "implicit" imports within the namespace package at the given path. - pub(crate) fn find(dir_path: &Path, exclusions: &[&Path]) -> io::Result { - let mut submodules: BTreeMap = BTreeMap::new(); - - // Enumerate all files and directories in the path, expanding links. - for entry in dir_path.read_dir()?.flatten() { - let file_type = entry.file_type()?; - - let path = entry.path(); - if exclusions.contains(&path.as_path()) { - continue; - } - - // TODO(charlie): Support symlinks. - if file_type.is_file() { - // Add implicit file-based modules. - let Some(extension) = path.extension() else { - continue; - }; - - let (file_stem, is_native_lib) = if extension == "py" || extension == "pyi" { - // E.g., `foo.py` becomes `foo`. - let file_stem = path.file_stem().and_then(OsStr::to_str); - let is_native_lib = false; - (file_stem, is_native_lib) - } else if native_module::is_native_module_file_extension(extension) { - // E.g., `foo.abi3.so` becomes `foo`. - let file_stem = native_module::native_module_name(&path); - let is_native_lib = true; - (file_stem, is_native_lib) - } else { - continue; - }; - - let Some(name) = file_stem else { - continue; - }; - - // Always prefer stub files over non-stub files. - if submodules - .get(name) - .is_none_or(|implicit_import| !implicit_import.is_stub_file) - { - submodules.insert( - name.to_string(), - ImplicitImport { - is_stub_file: extension == "pyi", - is_native_lib, - path, - py_typed: None, - }, - ); - } - } else if file_type.is_dir() { - // Add implicit directory-based modules. - let py_file_path = path.join("__init__.py"); - let pyi_file_path = path.join("__init__.pyi"); - - let (path, is_stub_file) = if py_file_path.exists() { - (py_file_path, false) - } else if pyi_file_path.exists() { - (pyi_file_path, true) - } else { - continue; - }; - - let Some(name) = path.file_name().and_then(OsStr::to_str) else { - continue; - }; - submodules.insert( - name.to_string(), - ImplicitImport { - is_stub_file, - is_native_lib: false, - py_typed: py_typed::get_py_typed_info(&path), - path, - }, - ); - } - } - - Ok(Self(submodules)) - } - - /// Filter [`ImplicitImports`] to only those symbols that were imported. - pub(crate) fn filter(&self, imported_symbols: &[String]) -> Option { - if self.is_empty() || imported_symbols.is_empty() { - return None; - } - - let filtered: BTreeMap = self - .iter() - .filter(|(name, _)| imported_symbols.contains(name)) - .map(|(name, implicit_import)| (name.clone(), implicit_import.clone())) - .collect(); - - if filtered.len() == self.len() { - return None; - } - - Some(Self(filtered)) - } - - /// Returns `true` if the [`ImplicitImports`] resolves all the symbols requested by a - /// module descriptor. - pub(crate) fn resolves_namespace_package(&self, imported_symbols: &[String]) -> bool { - if !imported_symbols.is_empty() { - // TODO(charlie): Pyright uses: - // - // ```typescript - // !Array.from(moduleDescriptor.importedSymbols.keys()).some((symbol) => implicitImports.has(symbol))` - // ``` - // - // However, that only checks if _any_ of the symbols are in the implicit imports. - for symbol in imported_symbols { - if !self.has(symbol) { - return false; - } - } - } else if self.is_empty() { - return false; - } - true - } - - /// Returns `true` if the module is present in the namespace package. - pub(crate) fn has(&self, name: &str) -> bool { - self.0.contains_key(name) - } - - /// Returns the number of implicit imports in the namespace package. - pub(crate) fn len(&self) -> usize { - self.0.len() - } - - /// Returns `true` if there are no implicit imports in the namespace package. - pub(crate) fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Returns an iterator over the implicit imports in the namespace package. - pub(crate) fn iter(&self) -> impl Iterator { - self.0.iter() - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct ImplicitImport { - /// Whether the implicit import is a stub file. - pub(crate) is_stub_file: bool, - - /// Whether the implicit import is a native module. - pub(crate) is_native_lib: bool, - - /// The path to the implicit import. - pub(crate) path: PathBuf, - - /// The `py.typed` information for the implicit import, if any. - pub(crate) py_typed: Option, -} diff --git a/crates/ruff_python_resolver/src/import_result.rs b/crates/ruff_python_resolver/src/import_result.rs deleted file mode 100644 index b0556d06fce849..00000000000000 --- a/crates/ruff_python_resolver/src/import_result.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Interface that describes the output of the import resolver. - -use std::path::PathBuf; - -use crate::implicit_imports::ImplicitImports; -use crate::py_typed::PyTypedInfo; - -#[derive(Debug, Clone, PartialEq, Eq)] -#[expect(clippy::struct_excessive_bools)] -pub(crate) struct ImportResult { - /// Whether the import name was relative (e.g., ".foo"). - pub(crate) is_relative: bool, - - /// Whether the import was resolved to a file or module. - pub(crate) is_import_found: bool, - - /// The path was partially resolved, but the specific submodule - /// defining the import was not found. For example, `foo.bar` was - /// not found, but `foo` was found. - pub(crate) is_partly_resolved: bool, - - /// The import refers to a namespace package (i.e., a folder without - /// an `__init__.py[i]` file at the final level of resolution). By - /// convention, we insert empty `PathBuf` segments into the resolved - /// paths vector to indicate intermediary namespace packages. - pub(crate) is_namespace_package: bool, - - /// The final resolved directory contains an `__init__.py[i]` file. - pub(crate) is_init_file_present: bool, - - /// The import resolved to a stub (`.pyi`) file within a stub package. - pub(crate) is_stub_package: bool, - - /// The import resolved to a built-in, local, or third-party module. - pub(crate) import_type: ImportType, - - /// A vector of resolved absolute paths for each file in the module - /// name. Typically includes a sequence of `__init__.py` files, followed - /// by the Python file defining the import itself, though the exact - /// structure can vary. For example, namespace packages will be represented - /// by empty `PathBuf` segments in the vector. - /// - /// For example, resolving `import foo.bar` might yield `./foo/__init__.py` and `./foo/bar.py`, - /// or `./foo/__init__.py` and `./foo/bar/__init__.py`. - pub(crate) resolved_paths: Vec, - - /// The search path used to resolve the module. - pub(crate) search_path: Option, - - /// The resolved file is a type hint (i.e., a `.pyi` file), rather - /// than a Python (`.py`) file. - pub(crate) is_stub_file: bool, - - /// The resolved file is a native library. - pub(crate) is_native_lib: bool, - - /// The resolved file is a hint hint (i.e., a `.pyi` file) from - /// `typeshed` in the standard library. - pub(crate) is_stdlib_typeshed_file: bool, - - /// The resolved file is a hint hint (i.e., a `.pyi` file) from - /// `typeshed` in third-party stubs. - pub(crate) is_third_party_typeshed_file: bool, - - /// The resolved file is a type hint (i.e., a `.pyi` file) from - /// the configured typing directory. - pub(crate) is_local_typings_file: bool, - - /// A map from file to resolved path, for all implicitly imported - /// modules that are part of a namespace package. - pub(crate) implicit_imports: ImplicitImports, - - /// Any implicit imports whose symbols were explicitly imported (i.e., via - /// a `from x import y` statement). - pub(crate) filtered_implicit_imports: ImplicitImports, - - /// If the import resolved to a type hint (i.e., a `.pyi` file), then - /// a non-type-hint resolution will be stored here. - #[expect(clippy::struct_field_names)] - pub(crate) non_stub_import_result: Option>, - - /// Information extracted from the `py.typed` in the package used to - /// resolve the import, if any. - pub(crate) py_typed_info: Option, - - /// The directory of the package, if any. - pub(crate) package_directory: Option, -} - -impl ImportResult { - /// An import result that indicates that the import was not found. - pub(crate) fn not_found() -> Self { - Self { - is_relative: false, - is_import_found: false, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: false, - is_stub_package: false, - import_type: ImportType::Local, - resolved_paths: vec![], - search_path: None, - is_stub_file: false, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports::default(), - filtered_implicit_imports: ImplicitImports::default(), - non_stub_import_result: None, - py_typed_info: None, - package_directory: None, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum ImportType { - BuiltIn, - ThirdParty, - Local, -} diff --git a/crates/ruff_python_resolver/src/lib.rs b/crates/ruff_python_resolver/src/lib.rs deleted file mode 100644 index f4d5992ac03896..00000000000000 --- a/crates/ruff_python_resolver/src/lib.rs +++ /dev/null @@ -1,948 +0,0 @@ -#![allow(dead_code)] - -mod config; -mod execution_environment; -mod host; -mod implicit_imports; -mod import_result; -mod module_descriptor; -mod native_module; -mod py_typed; -mod python_platform; -mod python_version; -mod resolver; -mod search; - -#[cfg(test)] -mod tests { - use std::fs::{File, create_dir_all}; - use std::io::{self, Write}; - use std::path::{Path, PathBuf}; - - use log::debug; - use tempfile::TempDir; - - use crate::config::Config; - use crate::execution_environment::ExecutionEnvironment; - use crate::host; - use crate::import_result::{ImportResult, ImportType}; - use crate::module_descriptor::ImportModuleDescriptor; - use crate::python_platform::PythonPlatform; - use crate::python_version::PythonVersion; - use crate::resolver::resolve_import; - - /// Create a file at the given path with the given content. - fn create(path: PathBuf, content: &str) -> io::Result { - if let Some(parent) = path.parent() { - create_dir_all(parent)?; - } - let mut f = File::create(&path)?; - f.write_all(content.as_bytes())?; - f.sync_all()?; - - Ok(path) - } - - /// Create an empty file at the given path. - fn empty(path: PathBuf) -> io::Result { - create(path, "") - } - - /// Create a partial `py.typed` file at the given path. - fn partial(path: PathBuf) -> io::Result { - create(path, "partial\n") - } - - /// Create a `py.typed` file at the given path. - fn typed(path: PathBuf) -> io::Result { - create(path, "# typed") - } - - #[derive(Debug, Default)] - struct ResolverOptions { - extra_paths: Vec, - library: Option, - stub_path: Option, - typeshed_path: Option, - venv_path: Option, - venv: Option, - } - - fn resolve_options( - source_file: impl AsRef, - name: &str, - root: impl Into, - options: ResolverOptions, - ) -> ImportResult { - let ResolverOptions { - extra_paths, - library, - stub_path, - typeshed_path, - venv_path, - venv, - } = options; - - let execution_environment = ExecutionEnvironment { - root: root.into(), - python_version: PythonVersion::Py37, - python_platform: PythonPlatform::Darwin, - extra_paths, - }; - - let module_descriptor = ImportModuleDescriptor { - leading_dots: name.chars().take_while(|c| *c == '.').count(), - name_parts: name - .chars() - .skip_while(|c| *c == '.') - .collect::() - .split('.') - .map(std::string::ToString::to_string) - .collect(), - imported_symbols: Vec::new(), - }; - - let config = Config { - typeshed_path, - stub_path, - venv_path, - venv, - }; - - let host = host::StaticHost::new(if let Some(library) = library { - vec![library] - } else { - Vec::new() - }); - - resolve_import( - source_file.as_ref(), - &execution_environment, - &module_descriptor, - &config, - &host, - ) - } - - fn setup() { - env_logger::builder().is_test(true).try_init().ok(); - } - - macro_rules! assert_debug_snapshot_normalize_paths { - ($value: ident) => {{ - // The debug representation for the backslash are two backslashes (escaping) - let $value = std::format!("{:#?}", $value).replace("\\\\", "/"); - insta::assert_snapshot!($value); - }}; - } - - #[test] - fn partial_stub_file_exists() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - partial(library.join("myLib-stubs/py.typed"))?; - let partial_stub_pyi = empty(library.join("myLib-stubs").join("partialStub.pyi"))?; - let partial_stub_py = empty(library.join("myLib/partialStub.py"))?; - - let result = resolve_options( - partial_stub_py, - "myLib.partialStub", - root, - ResolverOptions { - library: Some(library), - ..Default::default() - }, - ); - - assert!(result.is_import_found); - assert!(result.is_stub_file); - assert_eq!(result.import_type, ImportType::ThirdParty); - assert_eq!( - result.resolved_paths, - // TODO(charlie): Pyright matches on `libraryRoot, 'myLib', 'partialStub.pyi'` here. - // But that file doesn't exist. There's some kind of transform. - vec![PathBuf::new(), partial_stub_pyi] - ); - - Ok(()) - } - - #[test] - fn partial_stub_init_exists() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - partial(library.join("myLib-stubs/py.typed"))?; - let partial_stub_init_pyi = empty(library.join("myLib-stubs/__init__.pyi"))?; - let partial_stub_init_py = empty(library.join("myLib/__init__.py"))?; - - let result = resolve_options( - partial_stub_init_py, - "myLib", - root, - ResolverOptions { - library: Some(library), - ..Default::default() - }, - ); - - assert!(result.is_import_found); - assert!(result.is_stub_file); - assert_eq!(result.import_type, ImportType::ThirdParty); - assert_eq!( - result.resolved_paths, - // TODO(charlie): Pyright matches on `libraryRoot, 'myLib', '__init__.pyi'` here. - // But that file doesn't exist. There's some kind of transform. - vec![partial_stub_init_pyi] - ); - - Ok(()) - } - - #[test] - fn side_by_side_files() -> io::Result<()> { - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - partial(library.join("myLib-stubs/py.typed"))?; - empty(library.join("myLib/partialStub.pyi"))?; - empty(library.join("myLib/partialStub.py"))?; - empty(library.join("myLib/partialStub2.py"))?; - let my_file = empty(root.join("myFile.py"))?; - let side_by_side_stub_file = empty(library.join("myLib-stubs/partialStub.pyi"))?; - let partial_stub_file = empty(library.join("myLib-stubs/partialStub2.pyi"))?; - - // Stub package wins over original package (per PEP 561 rules). - let side_by_side_result = resolve_options( - &my_file, - "myLib.partialStub", - root, - ResolverOptions { - library: Some(library.clone()), - ..Default::default() - }, - ); - assert!(side_by_side_result.is_import_found); - assert!(side_by_side_result.is_stub_file); - assert_eq!( - side_by_side_result.resolved_paths, - vec![PathBuf::new(), side_by_side_stub_file] - ); - - // Side by side stub doesn't completely disable partial stub. - let partial_stub_result = resolve_options( - &my_file, - "myLib.partialStub2", - root, - ResolverOptions { - library: Some(library), - ..Default::default() - }, - ); - assert!(partial_stub_result.is_import_found); - assert!(partial_stub_result.is_stub_file); - assert_eq!( - partial_stub_result.resolved_paths, - vec![PathBuf::new(), partial_stub_file] - ); - - Ok(()) - } - - #[test] - fn stub_package() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - empty(library.join("myLib-stubs/stub.pyi"))?; - empty(library.join("myLib-stubs/__init__.pyi"))?; - let partial_stub_py = empty(library.join("myLib/partialStub.py"))?; - - let result = resolve_options( - partial_stub_py, - "myLib.partialStub", - root, - ResolverOptions { - library: Some(library), - ..Default::default() - }, - ); - - // If fully typed stub package exists, that wins over the real package. - assert!(!result.is_import_found); - - Ok(()) - } - - #[test] - fn stub_namespace_package() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - empty(library.join("myLib-stubs/stub.pyi"))?; - let partial_stub_py = empty(library.join("myLib/partialStub.py"))?; - - let result = resolve_options( - partial_stub_py.clone(), - "myLib.partialStub", - root, - ResolverOptions { - library: Some(library), - ..Default::default() - }, - ); - - // If fully typed stub package exists, that wins over the real package. - assert!(result.is_import_found); - assert!(!result.is_stub_file); - assert_eq!(result.resolved_paths, vec![PathBuf::new(), partial_stub_py]); - - Ok(()) - } - - #[test] - fn stub_in_typing_folder_over_partial_stub_package() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - let typing_folder = root.join("typing"); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - partial(library.join("myLib-stubs/py.typed"))?; - empty(library.join("myLib-stubs/__init__.pyi"))?; - let my_lib_pyi = empty(typing_folder.join("myLib.pyi"))?; - let my_lib_init_py = empty(library.join("myLib/__init__.py"))?; - - let result = resolve_options( - my_lib_init_py, - "myLib", - root, - ResolverOptions { - library: Some(library), - stub_path: Some(typing_folder), - ..Default::default() - }, - ); - - // If the package exists in typing folder, that gets picked up first (so we resolve to - // `myLib.pyi`). - assert!(result.is_import_found); - assert!(result.is_stub_file); - assert_eq!(result.resolved_paths, vec![my_lib_pyi]); - - Ok(()) - } - - #[test] - fn partial_stub_package_in_typing_folder() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - let typing_folder = root.join("typing"); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - partial(typing_folder.join("myLib-stubs/py.typed"))?; - let my_lib_stubs_init_pyi = empty(typing_folder.join("myLib-stubs/__init__.pyi"))?; - let my_lib_init_py = empty(library.join("myLib/__init__.py"))?; - - let result = resolve_options( - my_lib_init_py, - "myLib", - root, - ResolverOptions { - library: Some(library), - stub_path: Some(typing_folder), - ..Default::default() - }, - ); - - // If the package exists in typing folder, that gets picked up first (so we resolve to - // `myLib.pyi`). - assert!(result.is_import_found); - assert!(result.is_stub_file); - assert_eq!(result.resolved_paths, vec![my_lib_stubs_init_pyi]); - - Ok(()) - } - - #[test] - fn typeshed_folder() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - let typeshed_folder = root.join("ts"); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - empty(typeshed_folder.join("stubs/myLibPackage/myLib.pyi"))?; - partial(library.join("myLib-stubs/py.typed"))?; - let my_lib_stubs_init_pyi = empty(library.join("myLib-stubs/__init__.pyi"))?; - let my_lib_init_py = empty(library.join("myLib/__init__.py"))?; - - let result = resolve_options( - my_lib_init_py, - "myLib", - root, - ResolverOptions { - library: Some(library), - typeshed_path: Some(typeshed_folder), - ..Default::default() - }, - ); - - // Stub packages win over typeshed. - assert!(result.is_import_found); - assert!(result.is_stub_file); - assert_eq!(result.resolved_paths, vec![my_lib_stubs_init_pyi]); - - Ok(()) - } - - #[test] - fn py_typed_file() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - empty(library.join("myLib/__init__.py"))?; - partial(library.join("myLib-stubs/py.typed"))?; - let partial_stub_init_pyi = empty(library.join("myLib-stubs/__init__.pyi"))?; - let package_py_typed = typed(library.join("myLib/py.typed"))?; - - let result = resolve_options( - package_py_typed, - "myLib", - root, - ResolverOptions { - library: Some(library), - ..Default::default() - }, - ); - - // Partial stub package always overrides original package. - assert!(result.is_import_found); - assert!(result.is_stub_file); - assert_eq!(result.resolved_paths, vec![partial_stub_init_pyi]); - - Ok(()) - } - - #[test] - fn py_typed_library() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - let typeshed_folder = root.join("ts"); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - typed(library.join("os/py.typed"))?; - let init_py = empty(library.join("os/__init__.py"))?; - let typeshed_init_pyi = empty(typeshed_folder.join("stubs/os/os/__init__.pyi"))?; - - let result = resolve_options( - typeshed_init_pyi, - "os", - root, - ResolverOptions { - library: Some(library), - typeshed_path: Some(typeshed_folder), - ..Default::default() - }, - ); - - assert!(result.is_import_found); - assert_eq!(result.resolved_paths, vec![init_py]); - - Ok(()) - } - - #[test] - fn non_py_typed_library() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - let typeshed_folder = root.join("ts"); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - empty(library.join("os/__init__.py"))?; - let typeshed_init_pyi = empty(typeshed_folder.join("stubs/os/os/__init__.pyi"))?; - - let result = resolve_options( - typeshed_init_pyi.clone(), - "os", - root, - ResolverOptions { - library: Some(library), - typeshed_path: Some(typeshed_folder), - ..Default::default() - }, - ); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::ThirdParty); - assert_eq!(result.resolved_paths, vec![typeshed_init_pyi]); - - Ok(()) - } - - #[test] - fn import_side_by_side_file_root() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let file1 = empty(root.join("file1.py"))?; - let file2 = empty(root.join("file2.py"))?; - - let result = resolve_options(file2, "file1", root, ResolverOptions::default()); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::Local); - assert_eq!(result.resolved_paths, vec![file1]); - - Ok(()) - } - - #[test] - fn import_side_by_side_file_sub_folder() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let test_init = empty(root.join("test/__init__.py"))?; - let test_file1 = empty(root.join("test/file1.py"))?; - let test_file2 = empty(root.join("test/file2.py"))?; - - let result = resolve_options(test_file2, "test.file1", root, ResolverOptions::default()); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::Local); - assert_eq!(result.resolved_paths, vec![test_init, test_file1]); - - Ok(()) - } - - #[test] - fn import_side_by_side_file_sub_under_src_folder() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let nested_init = empty(root.join("src/nested/__init__.py"))?; - let nested_file1 = empty(root.join("src/nested/file1.py"))?; - let nested_file2 = empty(root.join("src/nested/file2.py"))?; - - let result = resolve_options( - nested_file2, - "nested.file1", - root, - ResolverOptions::default(), - ); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::Local); - assert_eq!(result.resolved_paths, vec![nested_init, nested_file1]); - - Ok(()) - } - - #[test] - fn import_file_sub_under_containing_folder() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let nested_file1 = empty(root.join("src/nested/file1.py"))?; - let nested_file2 = empty(root.join("src/nested/nested2/file2.py"))?; - - let result = resolve_options(nested_file2, "file1", root, ResolverOptions::default()); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::Local); - assert_eq!(result.resolved_paths, vec![nested_file1]); - - Ok(()) - } - - #[test] - fn import_side_by_side_file_sub_under_lib_folder() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let temp_dir = TempDir::new()?; - let library = temp_dir.path().join("lib").join("site-packages"); - - empty(library.join("myLib/file1.py"))?; - let file2 = empty(library.join("myLib/file2.py"))?; - - let result = resolve_options(file2, "file1", root, ResolverOptions::default()); - - debug!("result: {result:?}"); - - assert!(!result.is_import_found); - - Ok(()) - } - - #[test] - fn nested_namespace_package_1() -> io::Result<()> { - // See: https://github.com/microsoft/pyright/issues/5089. - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let file = empty(root.join("package1/a/b/c/d.py"))?; - let package1_init = empty(root.join("package1/a/__init__.py"))?; - let package2_init = empty(root.join("package2/a/__init__.py"))?; - - let package1 = root.join("package1"); - let package2 = root.join("package2"); - - let result = resolve_options( - package2_init, - "a.b.c.d", - root, - ResolverOptions { - extra_paths: vec![package1, package2], - ..Default::default() - }, - ); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::Local); - assert_eq!( - result.resolved_paths, - vec![package1_init, PathBuf::new(), PathBuf::new(), file] - ); - - Ok(()) - } - - #[test] - fn nested_namespace_package_2() -> io::Result<()> { - // See: https://github.com/microsoft/pyright/issues/5089. - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let file = empty(root.join("package1/a/b/c/d.py"))?; - let package1_init = empty(root.join("package1/a/b/c/__init__.py"))?; - let package2_init = empty(root.join("package2/a/b/c/__init__.py"))?; - - let package1 = root.join("package1"); - let package2 = root.join("package2"); - - let result = resolve_options( - package2_init, - "a.b.c.d", - root, - ResolverOptions { - extra_paths: vec![package1, package2], - ..Default::default() - }, - ); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::Local); - assert_eq!( - result.resolved_paths, - vec![PathBuf::new(), PathBuf::new(), package1_init, file] - ); - - Ok(()) - } - - #[test] - fn nested_namespace_package_3() -> io::Result<()> { - // See: https://github.com/microsoft/pyright/issues/5089. - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - empty(root.join("package1/a/b/c/d.py"))?; - let package2_init = empty(root.join("package2/a/__init__.py"))?; - - let package1 = root.join("package1"); - let package2 = root.join("package2"); - - let result = resolve_options( - package2_init, - "a.b.c.d", - root, - ResolverOptions { - extra_paths: vec![package1, package2], - ..Default::default() - }, - ); - - assert!(!result.is_import_found); - - Ok(()) - } - - #[test] - fn nested_namespace_package_4() -> io::Result<()> { - // See: https://github.com/microsoft/pyright/issues/5089. - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - empty(root.join("package1/a/b/__init__.py"))?; - empty(root.join("package1/a/b/c.py"))?; - empty(root.join("package2/a/__init__.py"))?; - let package2_a_b_init = empty(root.join("package2/a/b/__init__.py"))?; - - let package1 = root.join("package1"); - let package2 = root.join("package2"); - - let result = resolve_options( - package2_a_b_init, - "a.b.c", - root, - ResolverOptions { - extra_paths: vec![package1, package2], - ..Default::default() - }, - ); - - assert!(!result.is_import_found); - - Ok(()) - } - - // New tests, don't exist upstream. - #[test] - fn relative_import_side_by_side_file_root() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - let file1 = empty(root.join("file1.py"))?; - let file2 = empty(root.join("file2.py"))?; - - let result = resolve_options(file2, ".file1", root, ResolverOptions::default()); - - assert!(result.is_import_found); - assert_eq!(result.import_type, ImportType::Local); - assert_eq!(result.resolved_paths, vec![file1]); - - Ok(()) - } - - #[test] - fn invalid_relative_import_side_by_side_file_root() -> io::Result<()> { - setup(); - - let temp_dir = TempDir::new()?; - let root = temp_dir.path(); - - empty(root.join("file1.py"))?; - let file2 = empty(root.join("file2.py"))?; - - let result = resolve_options(file2, "..file1", root, ResolverOptions::default()); - - assert!(!result.is_import_found); - - Ok(()) - } - - #[test] - fn airflow_standard_library() { - setup(); - - let root = PathBuf::from("./resources/test/airflow"); - let source_file = root.join("airflow/api/common/mark_tasks.py"); - - let result = resolve_options( - source_file, - "os", - root.clone(), - ResolverOptions { - venv_path: Some(root), - venv: Some(PathBuf::from("venv")), - ..Default::default() - }, - ); - - assert_debug_snapshot_normalize_paths!(result); - } - - #[test] - fn airflow_first_party() { - setup(); - - let root = PathBuf::from("./resources/test/airflow"); - let source_file = root.join("airflow/api/common/mark_tasks.py"); - - let result = resolve_options( - source_file, - "airflow.jobs.scheduler_job_runner", - root.clone(), - ResolverOptions { - venv_path: Some(root), - venv: Some(PathBuf::from("venv")), - ..Default::default() - }, - ); - - assert_debug_snapshot_normalize_paths!(result); - } - - #[test] - fn airflow_stub_file() { - setup(); - - let root = PathBuf::from("./resources/test/airflow"); - let source_file = root.join("airflow/api/common/mark_tasks.py"); - - let result = resolve_options( - source_file, - "airflow.compat.functools", - root.clone(), - ResolverOptions { - venv_path: Some(root), - venv: Some(PathBuf::from("venv")), - ..Default::default() - }, - ); - - assert_debug_snapshot_normalize_paths!(result); - } - - #[test] - fn airflow_namespace_package() { - setup(); - - let root = PathBuf::from("./resources/test/airflow"); - let source_file = root.join("airflow/api/common/mark_tasks.py"); - - let result = resolve_options( - source_file, - "airflow.providers.google.cloud.hooks.gcs", - root.clone(), - ResolverOptions { - venv_path: Some(root), - venv: Some(PathBuf::from("venv")), - ..Default::default() - }, - ); - - assert_debug_snapshot_normalize_paths!(result); - } - - #[test] - fn airflow_third_party() { - setup(); - - let root = PathBuf::from("./resources/test/airflow"); - let source_file = root.join("airflow/api/common/mark_tasks.py"); - - let result = resolve_options( - source_file, - "sqlalchemy.orm", - root.clone(), - ResolverOptions { - venv_path: Some(root), - venv: Some(PathBuf::from("venv")), - ..Default::default() - }, - ); - - assert_debug_snapshot_normalize_paths!(result); - } - - #[test] - fn airflow_explicit_native_module() { - setup(); - - let root = PathBuf::from("./resources/test/airflow"); - let source_file = root.join("airflow/api/common/mark_tasks.py"); - - let result = resolve_options( - source_file, - "_watchdog_fsevents", - root.clone(), - ResolverOptions { - venv_path: Some(root), - venv: Some(PathBuf::from("venv")), - ..Default::default() - }, - ); - - assert_debug_snapshot_normalize_paths!(result); - } - - #[test] - fn airflow_implicit_native_module() { - setup(); - - let root = PathBuf::from("./resources/test/airflow"); - let source_file = root.join("airflow/api/common/mark_tasks.py"); - - let result = resolve_options( - source_file, - "orjson", - root.clone(), - ResolverOptions { - venv_path: Some(root), - venv: Some(PathBuf::from("venv")), - ..Default::default() - }, - ); - - assert_debug_snapshot_normalize_paths!(result); - } -} diff --git a/crates/ruff_python_resolver/src/module_descriptor.rs b/crates/ruff_python_resolver/src/module_descriptor.rs deleted file mode 100644 index 7d71efafbc0578..00000000000000 --- a/crates/ruff_python_resolver/src/module_descriptor.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct ImportModuleDescriptor { - pub(crate) leading_dots: usize, - pub(crate) name_parts: Vec, - pub(crate) imported_symbols: Vec, -} - -impl ImportModuleDescriptor { - pub(crate) fn name(&self) -> String { - format!( - "{}{}", - ".".repeat(self.leading_dots), - &self.name_parts.join(".") - ) - } -} diff --git a/crates/ruff_python_resolver/src/native_module.rs b/crates/ruff_python_resolver/src/native_module.rs deleted file mode 100644 index 6cb0b97efce652..00000000000000 --- a/crates/ruff_python_resolver/src/native_module.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Support for native Python extension modules. - -use std::ffi::OsStr; -use std::io; -use std::path::{Path, PathBuf}; - -/// Returns `true` if the given file extension is that of a native module. -pub(crate) fn is_native_module_file_extension(file_extension: &OsStr) -> bool { - file_extension == "so" || file_extension == "pyd" || file_extension == "dylib" -} - -/// Given a file name, returns the name of the native module it represents. -/// -/// For example, given `foo.abi3.so`, return `foo`. -pub(crate) fn native_module_name(file_name: &Path) -> Option<&str> { - file_name - .file_stem() - .and_then(OsStr::to_str) - .map(|file_stem| { - file_stem - .split_once('.') - .map_or(file_stem, |(file_stem, _)| file_stem) - }) -} - -/// Returns `true` if the given file name is that of a native module with the given name. -pub(crate) fn is_native_module_file_name(module_name: &str, file_name: &Path) -> bool { - // The file name must be that of a native module. - if !file_name - .extension() - .is_some_and(is_native_module_file_extension) - { - return false; - } - - // The file must represent the module name. - native_module_name(file_name) == Some(module_name) -} - -/// Find the native module within the namespace package at the given path. -pub(crate) fn find_native_module( - module_name: &str, - dir_path: &Path, -) -> io::Result> { - Ok(dir_path - .read_dir()? - .flatten() - .filter(|entry| entry.file_type().is_ok_and(|ft| ft.is_file())) - .map(|entry| entry.path()) - .find(|path| is_native_module_file_name(module_name, path))) -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - #[test] - fn module_name() { - assert_eq!( - super::native_module_name(&PathBuf::from("foo.so")), - Some("foo") - ); - - assert_eq!( - super::native_module_name(&PathBuf::from("foo.abi3.so")), - Some("foo") - ); - - assert_eq!( - super::native_module_name(&PathBuf::from("foo.cpython-38-x86_64-linux-gnu.so")), - Some("foo") - ); - - assert_eq!( - super::native_module_name(&PathBuf::from("foo.cp39-win_amd64.pyd")), - Some("foo") - ); - } - - #[test] - fn module_file_extension() { - assert!(super::is_native_module_file_extension("so".as_ref())); - assert!(super::is_native_module_file_extension("pyd".as_ref())); - assert!(super::is_native_module_file_extension("dylib".as_ref())); - assert!(!super::is_native_module_file_extension("py".as_ref())); - } -} diff --git a/crates/ruff_python_resolver/src/py_typed.rs b/crates/ruff_python_resolver/src/py_typed.rs deleted file mode 100644 index 258f801eed0662..00000000000000 --- a/crates/ruff_python_resolver/src/py_typed.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Support for [PEP 561] (`py.typed` files). -//! -//! [PEP 561]: https://peps.python.org/pep-0561/ - -use std::path::{Path, PathBuf}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct PyTypedInfo { - /// The path to the `py.typed` file. - py_typed_path: PathBuf, - - /// Whether the package is partially typed (as opposed to fully typed). - is_partially_typed: bool, -} - -/// Returns the `py.typed` information for the given directory, if any. -pub(crate) fn get_py_typed_info(dir_path: &Path) -> Option { - let py_typed_path = dir_path.join("py.typed"); - if py_typed_path.is_file() { - // Do a quick sanity check on the size before we attempt to read it. This - // file should always be really small - typically zero bytes in length. - let file_len = py_typed_path.metadata().ok()?.len(); - if file_len < 64 * 1024 { - // PEP 561 doesn't specify the format of "py.typed" in any detail other than - // to say that "If a stub package is partial it MUST include partial\n in a top - // level py.typed file." - let contents = std::fs::read_to_string(&py_typed_path).ok()?; - let is_partially_typed = - contents.contains("partial\n") || contents.contains("partial\r\n"); - Some(PyTypedInfo { - py_typed_path, - is_partially_typed, - }) - } else { - None - } - } else { - None - } -} diff --git a/crates/ruff_python_resolver/src/python_platform.rs b/crates/ruff_python_resolver/src/python_platform.rs deleted file mode 100644 index b82ebe256c1ccc..00000000000000 --- a/crates/ruff_python_resolver/src/python_platform.rs +++ /dev/null @@ -1,20 +0,0 @@ -/// Enum to represent a Python platform. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub(crate) enum PythonPlatform { - Darwin, - Linux, - Windows, -} - -impl PythonPlatform { - /// Returns the platform-specific library names. These are the candidate names for the top-level - /// subdirectory within a virtual environment that contains the `site-packages` directory - /// (with a `pythonX.Y` directory in-between). - pub(crate) fn lib_names(&self) -> &[&'static str] { - match self { - PythonPlatform::Darwin => &["lib"], - PythonPlatform::Linux => &["lib", "lib64"], - PythonPlatform::Windows => &["Lib"], - } - } -} diff --git a/crates/ruff_python_resolver/src/python_version.rs b/crates/ruff_python_resolver/src/python_version.rs deleted file mode 100644 index aeb2a76b75cf2d..00000000000000 --- a/crates/ruff_python_resolver/src/python_version.rs +++ /dev/null @@ -1,24 +0,0 @@ -/// Enum to represent a Python version. -#[derive(Debug, Copy, Clone)] -pub(crate) enum PythonVersion { - Py37, - Py38, - Py39, - Py310, - Py311, - Py312, -} - -impl PythonVersion { - /// The directory name (e.g., in a virtual environment) for this Python version. - pub(crate) fn dir(self) -> &'static str { - match self { - PythonVersion::Py37 => "python3.7", - PythonVersion::Py38 => "python3.8", - PythonVersion::Py39 => "python3.9", - PythonVersion::Py310 => "python3.10", - PythonVersion::Py311 => "python3.11", - PythonVersion::Py312 => "python3.12", - } - } -} diff --git a/crates/ruff_python_resolver/src/resolver.rs b/crates/ruff_python_resolver/src/resolver.rs deleted file mode 100644 index 5f97733663b304..00000000000000 --- a/crates/ruff_python_resolver/src/resolver.rs +++ /dev/null @@ -1,744 +0,0 @@ -//! Resolves Python imports to their corresponding files on disk. - -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; - -use log::debug; - -use crate::config::Config; -use crate::execution_environment::ExecutionEnvironment; -use crate::implicit_imports::ImplicitImports; -use crate::import_result::{ImportResult, ImportType}; -use crate::module_descriptor::ImportModuleDescriptor; -use crate::{host, native_module, py_typed, search}; - -#[expect(clippy::fn_params_excessive_bools)] -fn resolve_module_descriptor( - root: &Path, - module_descriptor: &ImportModuleDescriptor, - allow_partial: bool, - allow_native_lib: bool, - use_stub_package: bool, - allow_pyi: bool, - look_for_py_typed: bool, -) -> ImportResult { - if use_stub_package { - debug!("Attempting to resolve stub package using root path: {root:?}"); - } else { - debug!("Attempting to resolve using root path: {root:?}"); - } - - // Starting at the specified path, walk the file system to find the specified module. - let mut resolved_paths: Vec = Vec::new(); - let mut dir_path = root.to_path_buf(); - let mut is_namespace_package = false; - let mut is_init_file_present = false; - let mut is_stub_package = false; - let mut is_stub_file = false; - let mut is_native_lib = false; - let mut implicit_imports = None; - let mut package_directory = None; - let mut py_typed_info = None; - - // Ex) `from . import foo` - if module_descriptor.name_parts.is_empty() { - let py_file_path = dir_path.join("__init__.py"); - let pyi_file_path = dir_path.join("__init__.pyi"); - - if allow_pyi && pyi_file_path.is_file() { - debug!("Resolved import with file: {pyi_file_path:?}"); - resolved_paths.push(pyi_file_path.clone()); - } else if py_file_path.is_file() { - debug!("Resolved import with file: {py_file_path:?}"); - resolved_paths.push(py_file_path.clone()); - } else { - debug!("Partially resolved import with directory: {dir_path:?}"); - - // Add an empty path to indicate that the import is partially resolved. - resolved_paths.push(PathBuf::new()); - is_namespace_package = true; - } - - implicit_imports = ImplicitImports::find(&dir_path, &[&py_file_path, &pyi_file_path]).ok(); - } else { - for (i, part) in module_descriptor.name_parts.iter().enumerate() { - let is_first_part = i == 0; - let is_last_part = i == module_descriptor.name_parts.len() - 1; - - // Extend the directory path with the next segment. - let module_dir_path = if use_stub_package && is_first_part { - is_stub_package = true; - dir_path.join(format!("{part}-stubs")) - } else { - dir_path.join(part) - }; - - let found_directory = module_dir_path.is_dir(); - if found_directory { - if is_first_part { - package_directory = Some(module_dir_path.clone()); - } - - // Look for an `__init__.py[i]` in the directory. - let py_file_path = module_dir_path.join("__init__.py"); - let pyi_file_path = module_dir_path.join("__init__.pyi"); - is_init_file_present = false; - - if allow_pyi && pyi_file_path.is_file() { - debug!("Resolved import with file: {pyi_file_path:?}"); - resolved_paths.push(pyi_file_path.clone()); - if is_last_part { - is_stub_file = true; - } - is_init_file_present = true; - } else if py_file_path.is_file() { - debug!("Resolved import with file: {py_file_path:?}"); - resolved_paths.push(py_file_path.clone()); - is_init_file_present = true; - } - - if look_for_py_typed { - py_typed_info = - py_typed_info.or_else(|| py_typed::get_py_typed_info(&module_dir_path)); - } - - // We haven't reached the end of the import, and we found a matching directory. - // Proceed to the next segment. - if !is_last_part { - if !is_init_file_present { - resolved_paths.push(PathBuf::new()); - is_namespace_package = true; - py_typed_info = None; - } - - dir_path = module_dir_path; - continue; - } - - if is_init_file_present { - implicit_imports = - ImplicitImports::find(&module_dir_path, &[&py_file_path, &pyi_file_path]) - .ok(); - break; - } - } - - // We couldn't find a matching directory, or the directory didn't contain an - // `__init__.py[i]` file. Look for an `.py[i]` file with the same name as the - // segment, in lieu of a directory. - let py_file_path = module_dir_path.with_extension("py"); - let pyi_file_path = module_dir_path.with_extension("pyi"); - - if allow_pyi && pyi_file_path.is_file() { - debug!("Resolved import with file: {pyi_file_path:?}"); - resolved_paths.push(pyi_file_path); - if is_last_part { - is_stub_file = true; - } - } else if py_file_path.is_file() { - debug!("Resolved import with file: {py_file_path:?}"); - resolved_paths.push(py_file_path); - } else { - if allow_native_lib && dir_path.is_dir() { - // We couldn't find a `.py[i]` file; search for a native library. - if let Some(module_name) = module_dir_path.file_name().and_then(OsStr::to_str) { - if let Ok(Some(native_lib_path)) = - native_module::find_native_module(module_name, &dir_path) - { - debug!("Resolved import with file: {native_lib_path:?}"); - is_native_lib = true; - resolved_paths.push(native_lib_path); - } - } - } - - if !is_native_lib && found_directory { - debug!("Partially resolved import with directory: {dir_path:?}"); - resolved_paths.push(PathBuf::new()); - if is_last_part { - implicit_imports = - ImplicitImports::find(&dir_path, &[&py_file_path, &pyi_file_path]).ok(); - is_namespace_package = true; - } - } - } - - break; - } - } - - let import_found = if allow_partial { - !resolved_paths.is_empty() - } else { - resolved_paths.len() == module_descriptor.name_parts.len() - }; - - let is_partly_resolved = if resolved_paths.is_empty() { - false - } else { - resolved_paths.len() < module_descriptor.name_parts.len() - }; - - ImportResult { - is_relative: false, - is_import_found: import_found, - is_partly_resolved, - is_namespace_package, - is_init_file_present, - is_stub_package, - import_type: ImportType::Local, - resolved_paths, - search_path: Some(root.into()), - is_stub_file, - is_native_lib, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: implicit_imports.unwrap_or_default(), - filtered_implicit_imports: ImplicitImports::default(), - non_stub_import_result: None, - py_typed_info, - package_directory, - } -} - -/// Resolve an absolute module import based on the import resolution algorithm -/// defined in [PEP 420]. -/// -/// [PEP 420]: https://peps.python.org/pep-0420/ -#[expect(clippy::fn_params_excessive_bools)] -fn resolve_absolute_import( - root: &Path, - module_descriptor: &ImportModuleDescriptor, - allow_partial: bool, - allow_native_lib: bool, - use_stub_package: bool, - allow_pyi: bool, - look_for_py_typed: bool, -) -> ImportResult { - if allow_pyi && use_stub_package { - // Search for packaged stubs first. PEP 561 indicates that package authors can ship - // stubs separately from the package implementation by appending `-stubs` to its - // top-level directory name. - let import_result = resolve_module_descriptor( - root, - module_descriptor, - allow_partial, - false, - true, - true, - true, - ); - - if import_result.package_directory.is_some() { - // If this is a namespace package that wasn't resolved, assume that - // it's a partial stub package and continue looking for a real package. - if !import_result.is_namespace_package || import_result.is_import_found { - return import_result; - } - } - } - - // Search for a "real" package. - resolve_module_descriptor( - root, - module_descriptor, - allow_partial, - allow_native_lib, - false, - allow_pyi, - look_for_py_typed, - ) -} - -/// Resolve an absolute module import based on the import resolution algorithm, -/// taking into account the various competing files to which the import could -/// resolve. -/// -/// For example, prefers local imports over third-party imports, and stubs over -/// non-stubs. -fn resolve_best_absolute_import( - execution_environment: &ExecutionEnvironment, - module_descriptor: &ImportModuleDescriptor, - allow_pyi: bool, - config: &Config, - host: &Host, -) -> Option { - let import_name = module_descriptor.name(); - - // Search for local stub files (using `stub_path`). - if allow_pyi { - if let Some(stub_path) = config.stub_path.as_ref() { - debug!("Looking in stub path: {}", stub_path.display()); - - let mut typings_import = resolve_absolute_import( - stub_path, - module_descriptor, - false, - false, - true, - allow_pyi, - false, - ); - - if typings_import.is_import_found { - // Treat stub files as "local". - typings_import.import_type = ImportType::Local; - typings_import.is_local_typings_file = true; - - // If we resolved to a namespace package, ensure that all imported symbols are - // present in the namespace package's "implicit" imports. - if typings_import.is_namespace_package - && typings_import - .resolved_paths - .last() - .is_some_and(|path| path.as_os_str().is_empty()) - { - if typings_import - .implicit_imports - .resolves_namespace_package(&module_descriptor.imported_symbols) - { - return Some(typings_import); - } - } else { - return Some(typings_import); - } - } - - return None; - } - } - - // Look in the root directory of the execution environment. - debug!( - "Looking in root directory of execution environment: {}", - execution_environment.root.display() - ); - - let mut local_import = resolve_absolute_import( - &execution_environment.root, - module_descriptor, - false, - true, - true, - allow_pyi, - false, - ); - local_import.import_type = ImportType::Local; - - let mut best_result_so_far = Some(local_import); - - // Look in any extra paths. - for extra_path in &execution_environment.extra_paths { - debug!("Looking in extra path: {}", extra_path.display()); - - let mut local_import = resolve_absolute_import( - extra_path, - module_descriptor, - false, - true, - true, - allow_pyi, - false, - ); - local_import.import_type = ImportType::Local; - - best_result_so_far = Some(pick_best_import( - best_result_so_far, - local_import, - module_descriptor, - )); - } - - // Look for third-party imports in Python's `sys` path. - for search_path in search::python_search_paths(config, host) { - debug!("Looking in Python search path: {}", search_path.display()); - - let mut third_party_import = resolve_absolute_import( - &search_path, - module_descriptor, - false, - true, - true, - allow_pyi, - true, - ); - third_party_import.import_type = ImportType::ThirdParty; - - best_result_so_far = Some(pick_best_import( - best_result_so_far, - third_party_import, - module_descriptor, - )); - } - - // If a library is fully `py.typed`, prefer the current result. There's one exception: - // we're executing from `typeshed` itself. In that case, use the `typeshed` lookup below, - // rather than favoring `py.typed` libraries. - if let Some(typeshed_root) = search::typeshed_root(config, host) { - debug!( - "Looking in typeshed root directory: {}", - typeshed_root.display() - ); - if typeshed_root != execution_environment.root { - if best_result_so_far - .as_ref() - .is_some_and(|result| result.py_typed_info.is_some() && !result.is_partly_resolved) - { - return best_result_so_far; - } - } - } - - if allow_pyi && !module_descriptor.name_parts.is_empty() { - // Check for a stdlib typeshed file. - debug!("Looking for typeshed stdlib path: {import_name}"); - if let Some(mut typeshed_stdilib_import) = - find_typeshed_path(module_descriptor, true, config, host) - { - typeshed_stdilib_import.is_stdlib_typeshed_file = true; - return Some(typeshed_stdilib_import); - } - - // Check for a third-party typeshed file. - debug!("Looking for typeshed third-party path: {import_name}"); - if let Some(mut typeshed_third_party_import) = - find_typeshed_path(module_descriptor, false, config, host) - { - typeshed_third_party_import.is_third_party_typeshed_file = true; - - best_result_so_far = Some(pick_best_import( - best_result_so_far, - typeshed_third_party_import, - module_descriptor, - )); - } - } - - // We weren't able to find an exact match, so return the best - // partial match. - best_result_so_far -} - -/// Finds the `typeshed` path for the given module descriptor. -/// -/// Supports both standard library and third-party `typeshed` lookups. -fn find_typeshed_path( - module_descriptor: &ImportModuleDescriptor, - is_std_lib: bool, - config: &Config, - host: &Host, -) -> Option { - if is_std_lib { - debug!("Looking for typeshed `stdlib` path"); - } else { - debug!("Looking for typeshed `stubs` path"); - } - - let mut typeshed_paths = vec![]; - - if is_std_lib { - if let Some(path) = search::stdlib_typeshed_path(config, host) { - typeshed_paths.push(path); - } - } else { - if let Some(paths) = - search::third_party_typeshed_package_paths(module_descriptor, config, host) - { - typeshed_paths.extend(paths); - } - } - - for typeshed_path in typeshed_paths { - if typeshed_path.is_dir() { - let mut import_info = resolve_absolute_import( - &typeshed_path, - module_descriptor, - false, - false, - false, - true, - false, - ); - if import_info.is_import_found { - import_info.import_type = if is_std_lib { - ImportType::BuiltIn - } else { - ImportType::ThirdParty - }; - return Some(import_info); - } - } - } - - debug!("Typeshed path not found"); - None -} - -/// Given a current "best" import and a newly discovered result, returns the -/// preferred result. -fn pick_best_import( - best_import_so_far: Option, - new_import: ImportResult, - module_descriptor: &ImportModuleDescriptor, -) -> ImportResult { - let Some(best_import_so_far) = best_import_so_far else { - return new_import; - }; - - if new_import.is_import_found { - // Prefer traditional over namespace packages. - let so_far_index = best_import_so_far - .resolved_paths - .iter() - .position(|path| !path.as_os_str().is_empty()); - let new_index = new_import - .resolved_paths - .iter() - .position(|path| !path.as_os_str().is_empty()); - if so_far_index != new_index { - match (so_far_index, new_index) { - (None, Some(_)) => return new_import, - (Some(_), None) => return best_import_so_far, - (Some(so_far_index), Some(new_index)) => { - return if so_far_index < new_index { - best_import_so_far - } else { - new_import - }; - } - _ => {} - } - } - - // Prefer "found" over "not found". - if !best_import_so_far.is_import_found { - return new_import; - } - - // If both results are namespace imports, prefer the result that resolves all - // imported symbols. - if best_import_so_far.is_namespace_package && new_import.is_namespace_package { - if !module_descriptor.imported_symbols.is_empty() { - if !best_import_so_far - .implicit_imports - .resolves_namespace_package(&module_descriptor.imported_symbols) - { - if new_import - .implicit_imports - .resolves_namespace_package(&module_descriptor.imported_symbols) - { - return new_import; - } - - // Prefer the namespace package that has an `__init__.py[i]` file present in the - // final directory over one that does not. - if best_import_so_far.is_init_file_present && !new_import.is_init_file_present { - return best_import_so_far; - } - if !best_import_so_far.is_init_file_present && new_import.is_init_file_present { - return new_import; - } - } - } - } - - // Prefer "py.typed" over "non-py.typed". - if best_import_so_far.py_typed_info.is_some() && new_import.py_typed_info.is_none() { - return best_import_so_far; - } - if best_import_so_far.py_typed_info.is_none() && best_import_so_far.py_typed_info.is_some() - { - return new_import; - } - - // Prefer stub files (`.pyi`) over non-stub files (`.py`). - if best_import_so_far.is_stub_file && !new_import.is_stub_file { - return best_import_so_far; - } - if !best_import_so_far.is_stub_file && new_import.is_stub_file { - return new_import; - } - - // If we're still tied, prefer a shorter resolution path. - if best_import_so_far.resolved_paths.len() > new_import.resolved_paths.len() { - return new_import; - } - } else if new_import.is_partly_resolved { - let so_far_index = best_import_so_far - .resolved_paths - .iter() - .position(|path| !path.as_os_str().is_empty()); - let new_index = new_import - .resolved_paths - .iter() - .position(|path| !path.as_os_str().is_empty()); - if so_far_index != new_index { - match (so_far_index, new_index) { - (None, Some(_)) => return new_import, - (Some(_), None) => return best_import_so_far, - (Some(so_far_index), Some(new_index)) => { - return if so_far_index < new_index { - best_import_so_far - } else { - new_import - }; - } - _ => {} - } - } - } - - best_import_so_far -} - -/// Resolve a relative import. -fn resolve_relative_import( - source_file: &Path, - module_descriptor: &ImportModuleDescriptor, -) -> Option { - // Determine which search path this file is part of. - let mut directory = source_file; - for _ in 0..module_descriptor.leading_dots { - directory = directory.parent()?; - } - - // Now try to match the module parts from the current directory location. - let mut abs_import = resolve_absolute_import( - directory, - module_descriptor, - false, - true, - false, - true, - false, - ); - - if abs_import.is_stub_file { - // If we found a stub for a relative import, only search - // the same folder for the real module. Otherwise, it will - // error out on runtime. - abs_import.non_stub_import_result = Some(Box::new(resolve_absolute_import( - directory, - module_descriptor, - false, - true, - false, - false, - false, - ))); - } - - Some(abs_import) -} - -/// Resolve an absolute or relative import. -fn resolve_import_strict( - source_file: &Path, - execution_environment: &ExecutionEnvironment, - module_descriptor: &ImportModuleDescriptor, - config: &Config, - host: &Host, -) -> ImportResult { - let import_name = module_descriptor.name(); - - if module_descriptor.leading_dots > 0 { - debug!("Resolving relative import for: {import_name}"); - - let relative_import = resolve_relative_import(source_file, module_descriptor); - - if let Some(mut relative_import) = relative_import { - relative_import.is_relative = true; - return relative_import; - } - } else { - debug!("Resolving best absolute import for: {import_name}"); - - let best_import = resolve_best_absolute_import( - execution_environment, - module_descriptor, - true, - config, - host, - ); - - if let Some(mut best_import) = best_import { - if best_import.is_stub_file { - debug!("Resolving best non-stub absolute import for: {import_name}"); - - best_import.non_stub_import_result = Some(Box::new( - resolve_best_absolute_import( - execution_environment, - module_descriptor, - false, - config, - host, - ) - .unwrap_or_else(ImportResult::not_found), - )); - } - return best_import; - } - } - - ImportResult::not_found() -} - -/// Resolves an import, given the current file and the import descriptor. -/// -/// The algorithm is as follows: -/// -/// 1. If the import is relative, convert it to an absolute import. -/// 2. Find the "best" match for the import, allowing stub files. Search local imports, any -/// configured search paths, the Python path, the typeshed path, etc. -/// 3. If a stub file was found, find the "best" match for the import, disallowing stub files. -/// 4. If the import wasn't resolved, try to resolve it in the parent directory, then the parent's -/// parent, and so on, until the import root is reached. -pub(crate) fn resolve_import( - source_file: &Path, - execution_environment: &ExecutionEnvironment, - module_descriptor: &ImportModuleDescriptor, - config: &Config, - host: &Host, -) -> ImportResult { - let import_result = resolve_import_strict( - source_file, - execution_environment, - module_descriptor, - config, - host, - ); - if import_result.is_import_found || module_descriptor.leading_dots > 0 { - return import_result; - } - - // If we weren't able to resolve an absolute import, try resolving it in the - // importing file's directory, then the parent directory, and so on, until the - // import root is reached. - let root = execution_environment.root.as_path(); - let mut current = source_file; - while let Some(parent) = current.parent() { - if !parent.starts_with(root) { - break; - } - - debug!("Resolving absolute import in parent: {}", parent.display()); - - let mut result = - resolve_absolute_import(parent, module_descriptor, false, false, false, true, false); - - if result.is_import_found { - if let Some(implicit_imports) = result - .implicit_imports - .filter(&module_descriptor.imported_symbols) - { - result.implicit_imports = implicit_imports; - } - return result; - } - - current = parent; - } - - ImportResult::not_found() -} diff --git a/crates/ruff_python_resolver/src/search.rs b/crates/ruff_python_resolver/src/search.rs deleted file mode 100644 index 5c35fa7b582f59..00000000000000 --- a/crates/ruff_python_resolver/src/search.rs +++ /dev/null @@ -1,278 +0,0 @@ -//! Determine the appropriate search paths for the Python environment. - -use std::collections::HashMap; -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; -use std::{fs, io}; - -use log::debug; - -use crate::config::Config; -use crate::host; -use crate::module_descriptor::ImportModuleDescriptor; -use crate::python_version::PythonVersion; - -const SITE_PACKAGES: &str = "site-packages"; - -/// Find the `site-packages` directory for the specified Python version. -fn find_site_packages_path( - lib_path: &Path, - python_version: Option, -) -> Option { - if lib_path.is_dir() { - debug!( - "Found path `{}`; looking for site-packages", - lib_path.display() - ); - } else { - debug!("Did not find `{}`", lib_path.display()); - } - - let site_packages_path = lib_path.join(SITE_PACKAGES); - if site_packages_path.is_dir() { - debug!("Found path `{}`", site_packages_path.display()); - return Some(site_packages_path); - } - - debug!( - "Did not find `{}`, so looking for Python subdirectory", - site_packages_path.display() - ); - - // There's no `site-packages` directory in the library directory; look for a `python3.X` - // directory instead. - let candidate_dirs: Vec = fs::read_dir(lib_path) - .ok()? - .filter_map(|entry| { - let entry = entry.ok()?; - let metadata = entry.metadata().ok()?; - - if metadata.file_type().is_dir() { - let dir_path = entry.path(); - if dir_path - .file_name() - .and_then(OsStr::to_str)? - .starts_with("python3.") - { - if dir_path.join(SITE_PACKAGES).is_dir() { - return Some(dir_path); - } - } - } else if metadata.file_type().is_symlink() { - let symlink_path = fs::read_link(entry.path()).ok()?; - if symlink_path - .file_name() - .and_then(OsStr::to_str)? - .starts_with("python3.") - { - if symlink_path.join(SITE_PACKAGES).is_dir() { - return Some(symlink_path); - } - } - } - - None - }) - .collect(); - - // If a `python3.X` directory does exist (and `3.X` matches the current Python version), - // prefer it over any other Python directories. - if let Some(python_version) = python_version { - if let Some(preferred_dir) = candidate_dirs.iter().find(|dir| { - dir.file_name() - .and_then(OsStr::to_str) - .is_some_and(|name| name == python_version.dir()) - }) { - debug!("Found path `{}`", preferred_dir.display()); - return Some(preferred_dir.join(SITE_PACKAGES)); - } - } - - // Fallback to the first `python3.X` directory that we found. - let default_dir = candidate_dirs.first()?; - debug!("Found path `{}`", default_dir.display()); - Some(default_dir.join(SITE_PACKAGES)) -} - -fn find_paths_from_pth_files(parent_dir: &Path) -> io::Result + '_> { - Ok(parent_dir - .read_dir()? - .flatten() - .filter(|entry| { - // Collect all *.pth files. - let Ok(file_type) = entry.file_type() else { - return false; - }; - file_type.is_file() || file_type.is_symlink() - }) - .map(|entry| entry.path()) - .filter(|path| path.extension() == Some(OsStr::new("pth"))) - .filter(|path| { - // Skip all files that are much larger than expected. - let Ok(metadata) = path.metadata() else { - return false; - }; - let file_len = metadata.len(); - file_len > 0 && file_len < 64 * 1024 - }) - .filter_map(|path| { - let data = fs::read_to_string(path).ok()?; - for line in data.lines() { - let trimmed_line = line.trim(); - if !trimmed_line.is_empty() - && !trimmed_line.starts_with('#') - && !trimmed_line.starts_with("import") - { - let pth_path = parent_dir.join(trimmed_line); - if pth_path.is_dir() { - return Some(pth_path); - } - } - } - None - })) -} - -/// Find the Python search paths for the given virtual environment. -fn find_python_search_paths(config: &Config, host: &Host) -> Vec { - if let Some(venv_path) = config.venv_path.as_ref() { - if let Some(venv) = config.venv.as_ref() { - let mut found_paths = vec![]; - - for lib_name in host.python_platform().lib_names() { - let lib_path = venv_path.join(venv).join(lib_name); - if let Some(site_packages_path) = find_site_packages_path(&lib_path, None) { - // Add paths from any `.pth` files in each of the `site-packages` directories. - if let Ok(pth_paths) = find_paths_from_pth_files(&site_packages_path) { - found_paths.extend(pth_paths); - } - - // Add the `site-packages` directory to the search path. - found_paths.push(site_packages_path); - } - } - - if !found_paths.is_empty() { - found_paths.sort(); - found_paths.dedup(); - - debug!("Found the following `site-packages` dirs"); - for path in &found_paths { - debug!(" {}", path.display()); - } - - return found_paths; - } - } - } - - // Fall back to the Python interpreter. - host.python_search_paths() -} - -/// Determine the relevant Python search paths. -pub(crate) fn python_search_paths(config: &Config, host: &Host) -> Vec { - // TODO(charlie): Cache search paths. - find_python_search_paths(config, host) -} - -/// Determine the root of the `typeshed` directory. -pub(crate) fn typeshed_root(config: &Config, host: &Host) -> Option { - if let Some(typeshed_path) = config.typeshed_path.as_ref() { - // Did the user specify a typeshed path? - if typeshed_path.is_dir() { - return Some(typeshed_path.clone()); - } - } else { - // If not, we'll look in the Python search paths. - for python_search_path in python_search_paths(config, host) { - let possible_typeshed_path = python_search_path.join("typeshed"); - if possible_typeshed_path.is_dir() { - return Some(possible_typeshed_path); - } - } - } - - None -} - -/// Determine the current `typeshed` subdirectory. -fn typeshed_subdirectory( - is_stdlib: bool, - config: &Config, - host: &Host, -) -> Option { - let typeshed_path = - typeshed_root(config, host)?.join(if is_stdlib { "stdlib" } else { "stubs" }); - if typeshed_path.is_dir() { - Some(typeshed_path) - } else { - None - } -} - -/// Generate a map from PyPI-registered package name to a list of paths -/// containing the package's stubs. -fn build_typeshed_third_party_package_map( - third_party_dir: &Path, -) -> io::Result>> { - let mut package_map = HashMap::new(); - - // Iterate over every directory. - for outer_entry in fs::read_dir(third_party_dir)? { - let outer_entry = outer_entry?; - if outer_entry.file_type()?.is_dir() { - // Iterate over any subdirectory children. - for inner_entry in fs::read_dir(outer_entry.path())? { - let inner_entry = inner_entry?; - - if inner_entry.file_type()?.is_dir() { - package_map - .entry(inner_entry.file_name().to_string_lossy().to_string()) - .or_insert_with(Vec::new) - .push(outer_entry.path()); - } else if inner_entry.file_type()?.is_file() { - if inner_entry - .path() - .extension() - .is_some_and(|extension| extension == "pyi") - { - if let Some(stripped_file_name) = inner_entry - .path() - .file_stem() - .and_then(std::ffi::OsStr::to_str) - .map(std::string::ToString::to_string) - { - package_map - .entry(stripped_file_name) - .or_insert_with(Vec::new) - .push(outer_entry.path()); - } - } - } - } - } - } - - Ok(package_map) -} - -/// Determine the current `typeshed` subdirectory for a third-party package. -pub(crate) fn third_party_typeshed_package_paths( - module_descriptor: &ImportModuleDescriptor, - config: &Config, - host: &Host, -) -> Option> { - let typeshed_path = typeshed_subdirectory(false, config, host)?; - let package_paths = build_typeshed_third_party_package_map(&typeshed_path).ok()?; - let first_name_part = module_descriptor.name_parts.first().map(String::as_str)?; - package_paths.get(first_name_part).cloned() -} - -/// Determine the current `typeshed` subdirectory for the standard library. -pub(crate) fn stdlib_typeshed_path( - config: &Config, - host: &Host, -) -> Option { - typeshed_subdirectory(true, config, host) -} diff --git a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_explicit_native_module.snap b/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_explicit_native_module.snap deleted file mode 100644 index 195c5aa002ef6f..00000000000000 --- a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_explicit_native_module.snap +++ /dev/null @@ -1,34 +0,0 @@ ---- -source: crates/ruff_python_resolver/src/lib.rs -expression: result -snapshot_kind: text ---- -ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: false, - is_stub_package: false, - import_type: ThirdParty, - resolved_paths: [ - "./resources/test/airflow/venv/lib/python3.11/site-packages/_watchdog_fsevents.cpython-311-darwin.so", - ], - search_path: Some( - "./resources/test/airflow/venv/lib/python3.11/site-packages", - ), - is_stub_file: false, - is_native_lib: true, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - {}, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: None, - py_typed_info: None, - package_directory: None, -} diff --git a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_first_party.snap b/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_first_party.snap deleted file mode 100644 index e1719656625930..00000000000000 --- a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_first_party.snap +++ /dev/null @@ -1,38 +0,0 @@ ---- -source: crates/ruff_python_resolver/src/lib.rs -expression: result -snapshot_kind: text ---- -ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: true, - is_stub_package: false, - import_type: Local, - resolved_paths: [ - "./resources/test/airflow/airflow/__init__.py", - "./resources/test/airflow/airflow/jobs/__init__.py", - "./resources/test/airflow/airflow/jobs/scheduler_job_runner.py", - ], - search_path: Some( - "./resources/test/airflow", - ), - is_stub_file: false, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - {}, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: None, - py_typed_info: None, - package_directory: Some( - "./resources/test/airflow/airflow", - ), -} diff --git a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_implicit_native_module.snap b/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_implicit_native_module.snap deleted file mode 100644 index 97a2fb547be78f..00000000000000 --- a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_implicit_native_module.snap +++ /dev/null @@ -1,92 +0,0 @@ ---- -source: crates/ruff_python_resolver/src/lib.rs -expression: result -snapshot_kind: text ---- -ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: true, - is_stub_package: false, - import_type: ThirdParty, - resolved_paths: [ - "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson/__init__.pyi", - ], - search_path: Some( - "./resources/test/airflow/venv/lib/python3.11/site-packages", - ), - is_stub_file: true, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - { - "orjson": ImplicitImport { - is_stub_file: false, - is_native_lib: true, - path: "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so", - py_typed: None, - }, - }, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: Some( - ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: true, - is_stub_package: false, - import_type: ThirdParty, - resolved_paths: [ - "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson/__init__.py", - ], - search_path: Some( - "./resources/test/airflow/venv/lib/python3.11/site-packages", - ), - is_stub_file: false, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - { - "orjson": ImplicitImport { - is_stub_file: false, - is_native_lib: true, - path: "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so", - py_typed: None, - }, - }, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: None, - py_typed_info: Some( - PyTypedInfo { - py_typed_path: "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson/py.typed", - is_partially_typed: false, - }, - ), - package_directory: Some( - "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson", - ), - }, - ), - py_typed_info: Some( - PyTypedInfo { - py_typed_path: "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson/py.typed", - is_partially_typed: false, - }, - ), - package_directory: Some( - "./resources/test/airflow/venv/lib/python3.11/site-packages/orjson", - ), -} diff --git a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_namespace_package.snap b/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_namespace_package.snap deleted file mode 100644 index 5749d8157705d6..00000000000000 --- a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_namespace_package.snap +++ /dev/null @@ -1,41 +0,0 @@ ---- -source: crates/ruff_python_resolver/src/lib.rs -expression: result -snapshot_kind: text ---- -ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: true, - is_init_file_present: true, - is_stub_package: false, - import_type: Local, - resolved_paths: [ - "./resources/test/airflow/airflow/__init__.py", - "", - "./resources/test/airflow/airflow/providers/google/__init__.py", - "./resources/test/airflow/airflow/providers/google/cloud/__init__.py", - "./resources/test/airflow/airflow/providers/google/cloud/hooks/__init__.py", - "./resources/test/airflow/airflow/providers/google/cloud/hooks/gcs.py", - ], - search_path: Some( - "./resources/test/airflow", - ), - is_stub_file: false, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - {}, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: None, - py_typed_info: None, - package_directory: Some( - "./resources/test/airflow/airflow", - ), -} diff --git a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_standard_library.snap b/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_standard_library.snap deleted file mode 100644 index 561a79df02227c..00000000000000 --- a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_standard_library.snap +++ /dev/null @@ -1,30 +0,0 @@ ---- -source: crates/ruff_python_resolver/src/lib.rs -expression: result -snapshot_kind: text ---- -ImportResult { - is_relative: false, - is_import_found: false, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: false, - is_stub_package: false, - import_type: Local, - resolved_paths: [], - search_path: None, - is_stub_file: false, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - {}, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: None, - py_typed_info: None, - package_directory: None, -} diff --git a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_stub_file.snap b/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_stub_file.snap deleted file mode 100644 index 8b5c084c238a4c..00000000000000 --- a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_stub_file.snap +++ /dev/null @@ -1,72 +0,0 @@ ---- -source: crates/ruff_python_resolver/src/lib.rs -expression: result -snapshot_kind: text ---- -ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: true, - is_stub_package: false, - import_type: Local, - resolved_paths: [ - "./resources/test/airflow/airflow/__init__.py", - "./resources/test/airflow/airflow/compat/__init__.py", - "./resources/test/airflow/airflow/compat/functools.pyi", - ], - search_path: Some( - "./resources/test/airflow", - ), - is_stub_file: true, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - {}, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: Some( - ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: true, - is_stub_package: false, - import_type: Local, - resolved_paths: [ - "./resources/test/airflow/airflow/__init__.py", - "./resources/test/airflow/airflow/compat/__init__.py", - "./resources/test/airflow/airflow/compat/functools.py", - ], - search_path: Some( - "./resources/test/airflow", - ), - is_stub_file: false, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - {}, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: None, - py_typed_info: None, - package_directory: Some( - "./resources/test/airflow/airflow", - ), - }, - ), - py_typed_info: None, - package_directory: Some( - "./resources/test/airflow/airflow", - ), -} diff --git a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_third_party.snap b/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_third_party.snap deleted file mode 100644 index f625b0cce98dc0..00000000000000 --- a/crates/ruff_python_resolver/src/snapshots/ruff_python_resolver__tests__airflow_third_party.snap +++ /dev/null @@ -1,56 +0,0 @@ ---- -source: crates/ruff_python_resolver/src/lib.rs -expression: result -snapshot_kind: text ---- -ImportResult { - is_relative: false, - is_import_found: true, - is_partly_resolved: false, - is_namespace_package: false, - is_init_file_present: true, - is_stub_package: false, - import_type: ThirdParty, - resolved_paths: [ - "./resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/__init__.py", - "./resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/__init__.py", - ], - search_path: Some( - "./resources/test/airflow/venv/lib/python3.11/site-packages", - ), - is_stub_file: false, - is_native_lib: false, - is_stdlib_typeshed_file: false, - is_third_party_typeshed_file: false, - is_local_typings_file: false, - implicit_imports: ImplicitImports( - { - "base": ImplicitImport { - is_stub_file: false, - is_native_lib: false, - path: "./resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/base.py", - py_typed: None, - }, - "dependency": ImplicitImport { - is_stub_file: false, - is_native_lib: false, - path: "./resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/dependency.py", - py_typed: None, - }, - "query": ImplicitImport { - is_stub_file: false, - is_native_lib: false, - path: "./resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy/orm/query.py", - py_typed: None, - }, - }, - ), - filtered_implicit_imports: ImplicitImports( - {}, - ), - non_stub_import_result: None, - py_typed_info: None, - package_directory: Some( - "./resources/test/airflow/venv/lib/python3.11/site-packages/sqlalchemy", - ), -}