Skip to main content

ty_site_packages/
version.rs

1//! Types for representing the Python version and its source.
2
3use std::sync::Arc;
4
5use ruff_db::Db;
6use ruff_db::diagnostic::Span;
7use ruff_db::files::system_path_to_file;
8use ruff_db::system::SystemPathBuf;
9use ruff_python_ast::PythonVersion;
10use ruff_text_size::TextRange;
11
12/// The source of the Python version.
13#[derive(Clone, Debug, Eq, PartialEq, Hash, Default, get_size2::GetSize)]
14pub enum PythonVersionSource {
15    /// Value loaded from a project's configuration file.
16    ConfigFile(PythonVersionFileSource),
17
18    /// Value configured in a standalone script's inline metadata.
19    ScriptMetadata(Span),
20
21    /// Value loaded from the `pyvenv.cfg` file of the virtual environment.
22    /// The virtual environment might have been configured, activated or inferred.
23    PyvenvCfgFile(PythonVersionFileSource),
24
25    /// Value inferred from the layout of the Python installation.
26    ///
27    /// This only ever applies on Unix. On Unix, the `site-packages` directory
28    /// will always be at `sys.prefix/lib/pythonX.Y/site-packages`,
29    /// so we can infer the Python version from the parent directory of `site-packages`.
30    InstallationDirectoryLayout {
31        site_packages_parent_dir: Box<str>,
32        source: Option<PythonVersionFileSource>,
33    },
34
35    /// The value comes from a CLI argument, while it's left open if specified using a short argument,
36    /// long argument (`--extra-paths`) or `--config key=value`.
37    Cli,
38
39    /// The value comes from the user's editor,
40    /// while it's left open if specified as a setting
41    /// or if the value was auto-discovered by the editor
42    /// (e.g., the Python environment)
43    Editor,
44
45    /// The value was provided by `uv workspace metadata`.
46    UvWorkspace,
47
48    /// We fell back to a default value because the value was not specified via the CLI or a config file.
49    #[default]
50    Default,
51}
52
53/// Information regarding the file and [`TextRange`] of the configuration
54/// from which we inferred the Python version.
55#[derive(Debug, PartialEq, Eq, Hash, Clone, get_size2::GetSize)]
56pub struct PythonVersionFileSource {
57    path: Arc<SystemPathBuf>,
58    range: Option<TextRange>,
59}
60
61impl PythonVersionFileSource {
62    pub fn new(path: Arc<SystemPathBuf>, range: Option<TextRange>) -> Self {
63        Self { path, range }
64    }
65
66    /// Attempt to resolve a [`Span`] that corresponds to the location of
67    /// the configuration setting that specified the Python version.
68    ///
69    /// Useful for subdiagnostics when informing the user
70    /// what the inferred Python version of their project is.
71    pub fn span(&self, db: &dyn Db) -> Option<Span> {
72        let file = system_path_to_file(db, &*self.path).ok()?;
73        Some(Span::from(file).with_optional_range(self.range))
74    }
75}
76
77/// A Python version with its source.
78#[derive(Eq, PartialEq, Hash, Debug, Clone, get_size2::GetSize)]
79pub struct PythonVersionWithSource {
80    pub version: PythonVersion,
81    pub source: PythonVersionSource,
82}
83
84impl Default for PythonVersionWithSource {
85    fn default() -> Self {
86        Self {
87            version: PythonVersion::latest_ty(),
88            source: PythonVersionSource::Default,
89        }
90    }
91}