Delete the ruff_python_resolver crate (#18933)

This commit is contained in:
Micha Reiser 2025-06-25 12:53:13 +02:00 committed by GitHub
parent 689797a984
commit c1fed55d51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 0 additions and 3004 deletions

33
Cargo.lock generated
View file

@ -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"

View file

@ -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" }

View file

@ -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

View file

@ -1,3 +0,0 @@
# airflow
This is a mock subset of the Airflow repository, used to test module resolution.

View file

@ -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

View file

@ -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.

View file

@ -1 +0,0 @@
"""Empty file included to support filesystem-based resolver tests."""

View file

@ -1 +0,0 @@
"""Empty file included to support filesystem-based resolver tests."""

View file

@ -1 +0,0 @@
"""Empty file included to support filesystem-based resolver tests."""

View file

@ -1 +0,0 @@
"""Empty file included to support filesystem-based resolver tests."""

View file

@ -1 +0,0 @@
# Empty file included to support filesystem-based resolver tests.

View file

@ -1 +0,0 @@
# Empty file included to support filesystem-based resolver tests.

View file

@ -1 +0,0 @@
"""Empty file included to support filesystem-based resolver tests."""

View file

@ -1 +0,0 @@
"""Empty file included to support filesystem-based resolver tests."""

View file

@ -1 +0,0 @@
"""Empty file included to support filesystem-based resolver tests."""

View file

@ -1,18 +0,0 @@
use std::path::PathBuf;
pub(crate) struct Config {
/// Path to use for typeshed definitions.
pub(crate) typeshed_path: Option<PathBuf>,
/// Path to custom typings (stub) modules.
pub(crate) stub_path: Option<PathBuf>,
/// 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<PathBuf>,
/// Default venv environment.
pub(crate) venv: Option<PathBuf>,
}

View file

@ -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<PathBuf>,
}

View file

@ -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<PathBuf>;
/// 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<PathBuf>,
}
impl StaticHost {
pub(crate) fn new(search_paths: Vec<PathBuf>) -> Self {
Self { search_paths }
}
}
impl Host for StaticHost {
fn python_search_paths(&self) -> Vec<PathBuf> {
self.search_paths.clone()
}
fn python_version(&self) -> PythonVersion {
PythonVersion::Py312
}
fn python_platform(&self) -> PythonPlatform {
PythonPlatform::Darwin
}
}

View file

@ -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<String, ImplicitImport>);
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<Self> {
let mut submodules: BTreeMap<String, ImplicitImport> = 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<Self> {
if self.is_empty() || imported_symbols.is_empty() {
return None;
}
let filtered: BTreeMap<String, ImplicitImport> = 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<Item = (&String, &ImplicitImport)> {
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<py_typed::PyTypedInfo>,
}

View file

@ -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<PathBuf>,
/// The search path used to resolve the module.
pub(crate) search_path: Option<PathBuf>,
/// 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<Box<ImportResult>>,
/// Information extracted from the `py.typed` in the package used to
/// resolve the import, if any.
pub(crate) py_typed_info: Option<PyTypedInfo>,
/// The directory of the package, if any.
pub(crate) package_directory: Option<PathBuf>,
}
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,
}

View file

@ -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<PathBuf> {
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<PathBuf> {
create(path, "")
}
/// Create a partial `py.typed` file at the given path.
fn partial(path: PathBuf) -> io::Result<PathBuf> {
create(path, "partial\n")
}
/// Create a `py.typed` file at the given path.
fn typed(path: PathBuf) -> io::Result<PathBuf> {
create(path, "# typed")
}
#[derive(Debug, Default)]
struct ResolverOptions {
extra_paths: Vec<PathBuf>,
library: Option<PathBuf>,
stub_path: Option<PathBuf>,
typeshed_path: Option<PathBuf>,
venv_path: Option<PathBuf>,
venv: Option<PathBuf>,
}
fn resolve_options(
source_file: impl AsRef<Path>,
name: &str,
root: impl Into<PathBuf>,
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::<String>()
.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);
}
}

View file

@ -1,16 +0,0 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ImportModuleDescriptor {
pub(crate) leading_dots: usize,
pub(crate) name_parts: Vec<String>,
pub(crate) imported_symbols: Vec<String>,
}
impl ImportModuleDescriptor {
pub(crate) fn name(&self) -> String {
format!(
"{}{}",
".".repeat(self.leading_dots),
&self.name_parts.join(".")
)
}
}

View file

@ -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<Option<PathBuf>> {
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()));
}
}

View file

@ -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<PyTypedInfo> {
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
}
}

View file

@ -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"],
}
}
}

View file

@ -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",
}
}
}

View file

@ -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<PathBuf> = 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<Host: host::Host>(
execution_environment: &ExecutionEnvironment,
module_descriptor: &ImportModuleDescriptor,
allow_pyi: bool,
config: &Config,
host: &Host,
) -> Option<ImportResult> {
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<Host: host::Host>(
module_descriptor: &ImportModuleDescriptor,
is_std_lib: bool,
config: &Config,
host: &Host,
) -> Option<ImportResult> {
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<ImportResult>,
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<ImportResult> {
// 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<Host: host::Host>(
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<Host: host::Host>(
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()
}

View file

@ -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<PythonVersion>,
) -> Option<PathBuf> {
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<PathBuf> = 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<impl Iterator<Item = PathBuf> + '_> {
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<Host: host::Host>(config: &Config, host: &Host) -> Vec<PathBuf> {
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<Host: host::Host>(config: &Config, host: &Host) -> Vec<PathBuf> {
// TODO(charlie): Cache search paths.
find_python_search_paths(config, host)
}
/// Determine the root of the `typeshed` directory.
pub(crate) fn typeshed_root<Host: host::Host>(config: &Config, host: &Host) -> Option<PathBuf> {
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<Host: host::Host>(
is_stdlib: bool,
config: &Config,
host: &Host,
) -> Option<PathBuf> {
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<HashMap<String, Vec<PathBuf>>> {
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<Host: host::Host>(
module_descriptor: &ImportModuleDescriptor,
config: &Config,
host: &Host,
) -> Option<Vec<PathBuf>> {
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<Host: host::Host>(
config: &Config,
host: &Host,
) -> Option<PathBuf> {
typeshed_subdirectory(true, config, host)
}

View file

@ -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,
}

View file

@ -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",
),
}

View file

@ -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",
),
}

View file

@ -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",
),
}

View file

@ -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,
}

View file

@ -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",
),
}

View file

@ -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",
),
}