mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-19 19:44:40 +00:00
Add requires-python to uv init (#5322)
## Summary Prefers, in order: - The major-minor version of an interpreter discovered via `--python`. - The `requires-python` from the workspace. - The major-minor version of the default interpreter. If the `--python` request is a version or a version range, we use that without fetching an interpreter. Closes https://github.com/astral-sh/uv/issues/5299.
This commit is contained in:
parent
c8ac8ee57a
commit
0f8186d9ad
16 changed files with 461 additions and 103 deletions
|
|
@ -1797,6 +1797,20 @@ pub struct InitArgs {
|
|||
/// Do not create a readme file.
|
||||
#[arg(long)]
|
||||
pub no_readme: bool,
|
||||
|
||||
/// The Python interpreter to use to determine the minimum supported Python version.
|
||||
///
|
||||
/// By default, uv uses the virtual environment in the current working directory or any parent
|
||||
/// directory, falling back to searching for a Python executable in `PATH`. The `--python`
|
||||
/// option allows you to specify a different interpreter.
|
||||
///
|
||||
/// Supported formats:
|
||||
/// - `3.10` looks for an installed Python 3.10 using `py --list-paths` on Windows, or
|
||||
/// `python3.10` on Linux and macOS.
|
||||
/// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.
|
||||
/// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
|
||||
#[arg(long, short, env = "UV_PYTHON", verbatim_doc_comment)]
|
||||
pub python: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use once_cell::sync::Lazy;
|
|||
|
||||
use uv_configuration::PreviewMode;
|
||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||
use uv_workspace::ProjectWorkspace;
|
||||
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};
|
||||
|
||||
use crate::metadata::lowering::lower_requirement;
|
||||
use crate::metadata::MetadataError;
|
||||
|
|
@ -52,8 +52,12 @@ impl RequiresDist {
|
|||
) -> Result<Self, MetadataError> {
|
||||
// TODO(konsti): Limit discovery for Git checkouts to Git root.
|
||||
// TODO(konsti): Cache workspace discovery.
|
||||
let Some(project_workspace) =
|
||||
ProjectWorkspace::from_maybe_project_root(install_path, lock_path, None).await?
|
||||
let Some(project_workspace) = ProjectWorkspace::from_maybe_project_root(
|
||||
install_path,
|
||||
lock_path,
|
||||
&DiscoveryOptions::default(),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
return Ok(Self::from_metadata23(metadata));
|
||||
};
|
||||
|
|
@ -155,7 +159,7 @@ mod test {
|
|||
|
||||
use uv_configuration::PreviewMode;
|
||||
use uv_workspace::pyproject::PyProjectToml;
|
||||
use uv_workspace::ProjectWorkspace;
|
||||
use uv_workspace::{DiscoveryOptions, ProjectWorkspace};
|
||||
|
||||
use crate::RequiresDist;
|
||||
|
||||
|
|
@ -170,7 +174,10 @@ mod test {
|
|||
.as_ref()
|
||||
.context("metadata field project not found")?,
|
||||
&pyproject_toml,
|
||||
Some(path),
|
||||
&DiscoveryOptions {
|
||||
stop_discovery_at: Some(path),
|
||||
..DiscoveryOptions::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let requires_dist = pypi_types::RequiresDist::parse_pyproject_toml(contents)?;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,21 @@ impl RequiresPython {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a [`RequiresPython`] from a version specifier.
|
||||
pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Result<Self, RequiresPythonError> {
|
||||
let bound = RequiresPythonBound(
|
||||
crate::pubgrub::PubGrubSpecifier::from_release_specifiers(specifiers)?
|
||||
.iter()
|
||||
.next()
|
||||
.map(|(lower, _)| lower.clone())
|
||||
.unwrap_or(Bound::Unbounded),
|
||||
);
|
||||
Ok(Self {
|
||||
specifiers: specifiers.clone(),
|
||||
bound,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a [`RequiresPython`] to express the union of the given version specifiers.
|
||||
///
|
||||
/// For example, given `>=3.8` and `>=3.9`, this would return `>=3.8`.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
pub use workspace::{ProjectWorkspace, VirtualProject, Workspace, WorkspaceError, WorkspaceMember};
|
||||
pub use workspace::{
|
||||
DiscoveryOptions, ProjectWorkspace, VirtualProject, Workspace, WorkspaceError, WorkspaceMember,
|
||||
};
|
||||
|
||||
pub mod pyproject;
|
||||
pub mod pyproject_mut;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,14 @@ pub enum WorkspaceError {
|
|||
Normalize(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DiscoveryOptions<'a> {
|
||||
/// The path to stop discovery at.
|
||||
pub stop_discovery_at: Option<&'a Path>,
|
||||
/// The set of member paths to ignore.
|
||||
pub ignore: FxHashSet<&'a Path>,
|
||||
}
|
||||
|
||||
/// A workspace, consisting of a root directory and members. See [`ProjectWorkspace`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(serde::Serialize))]
|
||||
|
|
@ -80,7 +88,7 @@ impl Workspace {
|
|||
/// * If there is no explicit workspace: We have a single project workspace, we're done.
|
||||
pub async fn discover(
|
||||
path: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Workspace, WorkspaceError> {
|
||||
let path = absolutize_path(path)
|
||||
.map_err(WorkspaceError::Normalize)?
|
||||
|
|
@ -133,8 +141,7 @@ impl Workspace {
|
|||
} else if pyproject_toml.project.is_none() {
|
||||
// Without a project, it can't be an implicit root
|
||||
return Err(WorkspaceError::MissingProject(project_path));
|
||||
} else if let Some(workspace) = find_workspace(&project_path, stop_discovery_at).await?
|
||||
{
|
||||
} else if let Some(workspace) = find_workspace(&project_path, options).await? {
|
||||
// We have found an explicit root above.
|
||||
workspace
|
||||
} else {
|
||||
|
|
@ -168,7 +175,7 @@ impl Workspace {
|
|||
workspace_definition,
|
||||
workspace_pyproject_toml,
|
||||
current_project,
|
||||
stop_discovery_at,
|
||||
options,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
@ -345,11 +352,19 @@ impl Workspace {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the path is a workspace member.
|
||||
pub fn includes(&self, project_path: &Path) -> bool {
|
||||
self.packages
|
||||
.values()
|
||||
.any(|member| project_path == member.root())
|
||||
/// Returns `true` if the path is included by the workspace.
|
||||
pub fn includes(&self, project_path: &Path) -> Result<bool, WorkspaceError> {
|
||||
if let Some(workspace) = self
|
||||
.pyproject_toml
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| tool.uv.as_ref())
|
||||
.and_then(|uv| uv.workspace.as_ref())
|
||||
{
|
||||
is_included_in_workspace(project_path, &self.install_path, workspace)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect the workspace member projects from the `members` and `excludes` entries.
|
||||
|
|
@ -359,7 +374,7 @@ impl Workspace {
|
|||
workspace_definition: ToolUvWorkspace,
|
||||
workspace_pyproject_toml: PyProjectToml,
|
||||
current_project: Option<WorkspaceMember>,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Workspace, WorkspaceError> {
|
||||
let mut workspace_members = BTreeMap::new();
|
||||
// Avoid reading a `pyproject.toml` more than once.
|
||||
|
|
@ -421,6 +436,13 @@ impl Workspace {
|
|||
if !seen.insert(member_root.clone()) {
|
||||
continue;
|
||||
}
|
||||
if options.ignore.contains(member_root.as_path()) {
|
||||
debug!(
|
||||
"Ignoring workspace member: `{}`",
|
||||
member_root.simplified_display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let member_root = absolutize_path(&member_root)
|
||||
.map_err(WorkspaceError::Normalize)?
|
||||
.to_path_buf();
|
||||
|
|
@ -474,7 +496,7 @@ impl Workspace {
|
|||
.and_then(|uv| uv.sources)
|
||||
.unwrap_or_default();
|
||||
|
||||
check_nested_workspaces(&workspace_root, stop_discovery_at);
|
||||
check_nested_workspaces(&workspace_root, options);
|
||||
|
||||
Ok(Workspace {
|
||||
install_path: workspace_root,
|
||||
|
|
@ -612,13 +634,14 @@ impl ProjectWorkspace {
|
|||
/// only directories between the current path and `stop_discovery_at` are considered.
|
||||
pub async fn discover(
|
||||
path: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Self, WorkspaceError> {
|
||||
let project_root = path
|
||||
.ancestors()
|
||||
.take_while(|path| {
|
||||
// Only walk up the given directory, if any.
|
||||
stop_discovery_at
|
||||
options
|
||||
.stop_discovery_at
|
||||
.map(|stop_discovery_at| stop_discovery_at != *path)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
|
|
@ -630,13 +653,13 @@ impl ProjectWorkspace {
|
|||
project_root.simplified_display()
|
||||
);
|
||||
|
||||
Self::from_project_root(project_root, stop_discovery_at).await
|
||||
Self::from_project_root(project_root, options).await
|
||||
}
|
||||
|
||||
/// Discover the workspace starting from the directory containing the `pyproject.toml`.
|
||||
async fn from_project_root(
|
||||
project_root: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Self, WorkspaceError> {
|
||||
// Read the current `pyproject.toml`.
|
||||
let pyproject_path = project_root.join("pyproject.toml");
|
||||
|
|
@ -655,7 +678,7 @@ impl ProjectWorkspace {
|
|||
Path::new(""),
|
||||
&project,
|
||||
&pyproject_toml,
|
||||
stop_discovery_at,
|
||||
options,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
@ -665,7 +688,7 @@ impl ProjectWorkspace {
|
|||
pub async fn from_maybe_project_root(
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Option<Self>, WorkspaceError> {
|
||||
// Read the `pyproject.toml`.
|
||||
let pyproject_path = install_path.join("pyproject.toml");
|
||||
|
|
@ -683,14 +706,7 @@ impl ProjectWorkspace {
|
|||
};
|
||||
|
||||
Ok(Some(
|
||||
Self::from_project(
|
||||
install_path,
|
||||
lock_path,
|
||||
&project,
|
||||
&pyproject_toml,
|
||||
stop_discovery_at,
|
||||
)
|
||||
.await?,
|
||||
Self::from_project(install_path, lock_path, &project, &pyproject_toml, options).await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -721,7 +737,7 @@ impl ProjectWorkspace {
|
|||
lock_path: &Path,
|
||||
project: &Project,
|
||||
project_pyproject_toml: &PyProjectToml,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Self, WorkspaceError> {
|
||||
let project_path = absolutize_path(install_path)
|
||||
.map_err(WorkspaceError::Normalize)?
|
||||
|
|
@ -756,7 +772,7 @@ impl ProjectWorkspace {
|
|||
if workspace.is_none() {
|
||||
// The project isn't an explicit workspace root, check if we're a regular workspace
|
||||
// member by looking for an explicit workspace root above.
|
||||
workspace = find_workspace(&project_path, stop_discovery_at).await?;
|
||||
workspace = find_workspace(&project_path, options).await?;
|
||||
}
|
||||
|
||||
let current_project = WorkspaceMember {
|
||||
|
|
@ -819,7 +835,7 @@ impl ProjectWorkspace {
|
|||
workspace_definition,
|
||||
workspace_pyproject_toml,
|
||||
Some(current_project),
|
||||
stop_discovery_at,
|
||||
options,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -834,14 +850,15 @@ impl ProjectWorkspace {
|
|||
/// Find the workspace root above the current project, if any.
|
||||
async fn find_workspace(
|
||||
project_root: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Option<(PathBuf, ToolUvWorkspace, PyProjectToml)>, WorkspaceError> {
|
||||
// Skip 1 to ignore the current project itself.
|
||||
for workspace_root in project_root
|
||||
.ancestors()
|
||||
.take_while(|path| {
|
||||
// Only walk up the given directory, if any.
|
||||
stop_discovery_at
|
||||
options
|
||||
.stop_discovery_at
|
||||
.map(|stop_discovery_at| stop_discovery_at != *path)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
|
|
@ -919,12 +936,13 @@ async fn find_workspace(
|
|||
}
|
||||
|
||||
/// Warn when the valid workspace is included in another workspace.
|
||||
fn check_nested_workspaces(inner_workspace_root: &Path, stop_discovery_at: Option<&Path>) {
|
||||
fn check_nested_workspaces(inner_workspace_root: &Path, options: &DiscoveryOptions) {
|
||||
for outer_workspace_root in inner_workspace_root
|
||||
.ancestors()
|
||||
.take_while(|path| {
|
||||
// Only walk up the given directory, if any.
|
||||
stop_discovery_at
|
||||
options
|
||||
.stop_discovery_at
|
||||
.map(|stop_discovery_at| stop_discovery_at != *path)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
|
|
@ -1013,6 +1031,31 @@ fn is_excluded_from_workspace(
|
|||
Ok(false)
|
||||
}
|
||||
|
||||
/// Check if we're in the `tool.uv.workspace.members` of a workspace.
|
||||
fn is_included_in_workspace(
|
||||
project_path: &Path,
|
||||
workspace_root: &Path,
|
||||
workspace: &ToolUvWorkspace,
|
||||
) -> Result<bool, WorkspaceError> {
|
||||
for member_glob in workspace.members.iter().flatten() {
|
||||
let absolute_glob = workspace_root
|
||||
.simplified()
|
||||
.join(member_glob.as_str())
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
for member_root in glob(&absolute_glob)
|
||||
.map_err(|err| WorkspaceError::Pattern(absolute_glob.to_string(), err))?
|
||||
{
|
||||
let member_root =
|
||||
member_root.map_err(|err| WorkspaceError::Glob(absolute_glob.to_string(), err))?;
|
||||
if member_root == project_path {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// A project that can be synced.
|
||||
///
|
||||
/// The project could be a package within a workspace, a real workspace root, or even a virtual
|
||||
|
|
@ -1035,7 +1078,7 @@ impl VirtualProject {
|
|||
/// discovering the main workspace.
|
||||
pub async fn discover(
|
||||
path: &Path,
|
||||
stop_discovery_at: Option<&Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Self, WorkspaceError> {
|
||||
assert!(
|
||||
path.is_absolute(),
|
||||
|
|
@ -1045,7 +1088,8 @@ impl VirtualProject {
|
|||
.ancestors()
|
||||
.take_while(|path| {
|
||||
// Only walk up the given directory, if any.
|
||||
stop_discovery_at
|
||||
options
|
||||
.stop_discovery_at
|
||||
.map(|stop_discovery_at| stop_discovery_at != *path)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
|
|
@ -1070,7 +1114,7 @@ impl VirtualProject {
|
|||
Path::new(""),
|
||||
project,
|
||||
&pyproject_toml,
|
||||
stop_discovery_at,
|
||||
options,
|
||||
)
|
||||
.await?;
|
||||
Ok(Self::Project(project))
|
||||
|
|
@ -1091,7 +1135,7 @@ impl VirtualProject {
|
|||
workspace.clone(),
|
||||
pyproject_toml,
|
||||
None,
|
||||
stop_discovery_at,
|
||||
options,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -1135,7 +1179,7 @@ mod tests {
|
|||
|
||||
use insta::assert_json_snapshot;
|
||||
|
||||
use crate::workspace::ProjectWorkspace;
|
||||
use crate::workspace::{DiscoveryOptions, ProjectWorkspace};
|
||||
|
||||
async fn workspace_test(folder: &str) -> (ProjectWorkspace, String) {
|
||||
let root_dir = env::current_dir()
|
||||
|
|
@ -1146,7 +1190,8 @@ mod tests {
|
|||
.unwrap()
|
||||
.join("scripts")
|
||||
.join("workspaces");
|
||||
let project = ProjectWorkspace::discover(&root_dir.join(folder), None)
|
||||
let project =
|
||||
ProjectWorkspace::discover(&root_dir.join(folder), &DiscoveryOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
let root_escaped = regex::escape(root_dir.to_string_lossy().as_ref());
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use uv_types::{BuildIsolation, HashStrategy};
|
|||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::pyproject::{DependencyType, Source, SourceError};
|
||||
use uv_workspace::pyproject_mut::PyProjectTomlMut;
|
||||
use uv_workspace::{ProjectWorkspace, VirtualProject, Workspace};
|
||||
use uv_workspace::{DiscoveryOptions, ProjectWorkspace, VirtualProject, Workspace};
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::pip::resolution_environment;
|
||||
|
|
@ -54,12 +54,12 @@ pub(crate) async fn add(
|
|||
|
||||
// Find the project in the workspace.
|
||||
let project = if let Some(package) = package {
|
||||
Workspace::discover(&std::env::current_dir()?, None)
|
||||
Workspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default())
|
||||
.await?
|
||||
.with_current_project(package.clone())
|
||||
.with_context(|| format!("Package `{package}` not found in workspace"))?
|
||||
} else {
|
||||
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
|
||||
ProjectWorkspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default()).await?
|
||||
};
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
|
|
|
|||
|
|
@ -3,14 +3,23 @@ use std::path::PathBuf;
|
|||
|
||||
use anyhow::{Context, Result};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::PackageName;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
use uv_configuration::PreviewMode;
|
||||
use uv_fs::{absolutize_path, Simplified};
|
||||
use uv_python::{
|
||||
EnvironmentPreference, PythonFetch, PythonInstallation, PythonPreference, PythonRequest,
|
||||
VersionRequest,
|
||||
};
|
||||
use uv_resolver::RequiresPython;
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::pyproject_mut::PyProjectTomlMut;
|
||||
use uv_workspace::{Workspace, WorkspaceError};
|
||||
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceError};
|
||||
|
||||
use crate::commands::project::find_requires_python;
|
||||
use crate::commands::reporters::PythonDownloadReporter;
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
||||
|
|
@ -20,8 +29,14 @@ pub(crate) async fn init(
|
|||
explicit_path: Option<String>,
|
||||
name: Option<PackageName>,
|
||||
no_readme: bool,
|
||||
python: Option<String>,
|
||||
isolated: bool,
|
||||
preview: PreviewMode,
|
||||
python_preference: PythonPreference,
|
||||
python_fetch: PythonFetch,
|
||||
connectivity: Connectivity,
|
||||
native_tls: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
if preview.is_disabled() {
|
||||
|
|
@ -62,36 +77,111 @@ pub(crate) async fn init(
|
|||
}
|
||||
};
|
||||
|
||||
// Create the `pyproject.toml`.
|
||||
let pyproject = indoc::formatdoc! {r#"
|
||||
[project]
|
||||
name = "{name}"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"{readme}
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
"#,
|
||||
readme = if no_readme { "" } else { "\nreadme = \"README.md\"" },
|
||||
};
|
||||
|
||||
fs_err::create_dir_all(&path)?;
|
||||
fs_err::write(path.join("pyproject.toml"), pyproject)?;
|
||||
|
||||
// Discover the current workspace, if it exists.
|
||||
let workspace = if isolated {
|
||||
None
|
||||
} else {
|
||||
// Attempt to find a workspace root.
|
||||
let parent = path.parent().expect("Project path has no parent");
|
||||
match Workspace::discover(parent, None).await {
|
||||
match Workspace::discover(
|
||||
parent,
|
||||
&DiscoveryOptions {
|
||||
ignore: std::iter::once(path.as_ref()).collect(),
|
||||
..DiscoveryOptions::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(workspace) => Some(workspace),
|
||||
Err(WorkspaceError::MissingPyprojectToml) => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
};
|
||||
|
||||
// Add a `requires-python` field to the `pyproject.toml`.
|
||||
let requires_python = if let Some(request) = python.as_deref() {
|
||||
// (1) Explicit request from user
|
||||
match PythonRequest::parse(request) {
|
||||
PythonRequest::Version(VersionRequest::MajorMinor(major, minor)) => {
|
||||
RequiresPython::greater_than_equal_version(&Version::new([
|
||||
u64::from(major),
|
||||
u64::from(minor),
|
||||
]))
|
||||
}
|
||||
PythonRequest::Version(VersionRequest::MajorMinorPatch(major, minor, patch)) => {
|
||||
RequiresPython::greater_than_equal_version(&Version::new([
|
||||
u64::from(major),
|
||||
u64::from(minor),
|
||||
u64::from(patch),
|
||||
]))
|
||||
}
|
||||
PythonRequest::Version(VersionRequest::Range(specifiers)) => {
|
||||
RequiresPython::from_specifiers(&specifiers)?
|
||||
}
|
||||
request => {
|
||||
let reporter = PythonDownloadReporter::single(printer);
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls);
|
||||
let interpreter = PythonInstallation::find_or_fetch(
|
||||
Some(request),
|
||||
EnvironmentPreference::Any,
|
||||
python_preference,
|
||||
python_fetch,
|
||||
&client_builder,
|
||||
cache,
|
||||
Some(&reporter),
|
||||
)
|
||||
.await?
|
||||
.into_interpreter();
|
||||
RequiresPython::greater_than_equal_version(&interpreter.python_minor_version())
|
||||
}
|
||||
}
|
||||
} else if let Some(requires_python) = workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| find_requires_python(workspace).ok().flatten())
|
||||
{
|
||||
// (2) `Requires-Python` from the workspace
|
||||
requires_python
|
||||
} else {
|
||||
// (3) Default to the system Python
|
||||
let request = PythonRequest::Any;
|
||||
let reporter = PythonDownloadReporter::single(printer);
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls);
|
||||
let interpreter = PythonInstallation::find_or_fetch(
|
||||
Some(request),
|
||||
EnvironmentPreference::Any,
|
||||
python_preference,
|
||||
python_fetch,
|
||||
&client_builder,
|
||||
cache,
|
||||
Some(&reporter),
|
||||
)
|
||||
.await?
|
||||
.into_interpreter();
|
||||
RequiresPython::greater_than_equal_version(&interpreter.python_minor_version())
|
||||
};
|
||||
|
||||
// Create the `pyproject.toml`.
|
||||
let pyproject = indoc::formatdoc! {r#"
|
||||
[project]
|
||||
name = "{name}"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"{readme}
|
||||
requires-python = "{requires_python}"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
"#,
|
||||
readme = if no_readme { "" } else { "\nreadme = \"README.md\"" },
|
||||
requires_python = requires_python.specifiers(),
|
||||
};
|
||||
fs_err::create_dir_all(&path)?;
|
||||
fs_err::write(path.join("pyproject.toml"), pyproject)?;
|
||||
|
||||
// Create `src/{name}/__init__.py` if it does not already exist.
|
||||
let src_dir = path.join("src").join(&*name.as_dist_info_name());
|
||||
let init_py = src_dir.join("__init__.py");
|
||||
|
|
@ -123,7 +213,7 @@ pub(crate) async fn init(
|
|||
name.cyan(),
|
||||
workspace.install_path().simplified_display().cyan()
|
||||
)?;
|
||||
} else if workspace.includes(&path) {
|
||||
} else if workspace.includes(&path)? {
|
||||
// If the member is already included in the workspace, skip the `members` addition.
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use uv_resolver::{
|
|||
};
|
||||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
|
||||
use uv_warnings::{warn_user, warn_user_once};
|
||||
use uv_workspace::Workspace;
|
||||
use uv_workspace::{DiscoveryOptions, Workspace};
|
||||
|
||||
use crate::commands::project::{find_requires_python, FoundInterpreter, ProjectError, SharedState};
|
||||
use crate::commands::{pip, ExitStatus};
|
||||
|
|
@ -51,7 +51,8 @@ pub(crate) async fn lock(
|
|||
}
|
||||
|
||||
// Find the project requirements.
|
||||
let workspace = Workspace::discover(&std::env::current_dir()?, None).await?;
|
||||
let workspace =
|
||||
Workspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default()).await?;
|
||||
|
||||
// Find an interpreter for the project
|
||||
let interpreter = FoundInterpreter::discover(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use uv_python::{PythonFetch, PythonPreference, PythonRequest};
|
|||
use uv_warnings::{warn_user, warn_user_once};
|
||||
use uv_workspace::pyproject::DependencyType;
|
||||
use uv_workspace::pyproject_mut::PyProjectTomlMut;
|
||||
use uv_workspace::{ProjectWorkspace, VirtualProject, Workspace};
|
||||
use uv_workspace::{DiscoveryOptions, ProjectWorkspace, VirtualProject, Workspace};
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::{project, ExitStatus, SharedState};
|
||||
|
|
@ -39,12 +39,12 @@ pub(crate) async fn remove(
|
|||
|
||||
// Find the project in the workspace.
|
||||
let project = if let Some(package) = package {
|
||||
Workspace::discover(&std::env::current_dir()?, None)
|
||||
Workspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default())
|
||||
.await?
|
||||
.with_current_project(package.clone())
|
||||
.with_context(|| format!("Package `{package}` not found in workspace"))?
|
||||
} else {
|
||||
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
|
||||
ProjectWorkspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default()).await?
|
||||
};
|
||||
|
||||
let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use uv_python::{
|
|||
};
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::{VirtualProject, Workspace, WorkspaceError};
|
||||
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceError};
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::project::environment::CachedEnvironment;
|
||||
|
|
@ -144,13 +144,15 @@ pub(crate) async fn run(
|
|||
// We need a workspace, but we don't need to have a current package, we can be e.g. in
|
||||
// the root of a virtual workspace and then switch into the selected package.
|
||||
Some(VirtualProject::Project(
|
||||
Workspace::discover(&std::env::current_dir()?, None)
|
||||
Workspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default())
|
||||
.await?
|
||||
.with_current_project(package.clone())
|
||||
.with_context(|| format!("Package `{package}` not found in workspace"))?,
|
||||
))
|
||||
} else {
|
||||
match VirtualProject::discover(&std::env::current_dir()?, None).await {
|
||||
match VirtualProject::discover(&std::env::current_dir()?, &DiscoveryOptions::default())
|
||||
.await
|
||||
{
|
||||
Ok(project) => Some(project),
|
||||
Err(WorkspaceError::MissingPyprojectToml) => None,
|
||||
Err(WorkspaceError::NonWorkspace(_)) => None,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use uv_python::{PythonEnvironment, PythonFetch, PythonPreference, PythonRequest}
|
|||
use uv_resolver::{FlatIndex, Lock};
|
||||
use uv_types::{BuildIsolation, HashStrategy};
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::VirtualProject;
|
||||
use uv_workspace::{DiscoveryOptions, VirtualProject};
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::project::lock::do_safe_lock;
|
||||
|
|
@ -45,7 +45,8 @@ pub(crate) async fn sync(
|
|||
}
|
||||
|
||||
// Identify the project
|
||||
let project = VirtualProject::discover(&std::env::current_dir()?, None).await?;
|
||||
let project =
|
||||
VirtualProject::discover(&std::env::current_dir()?, &DiscoveryOptions::default()).await?;
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
let venv = project::get_or_init_environment(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use uv_client::Connectivity;
|
|||
use uv_configuration::{Concurrency, PreviewMode};
|
||||
use uv_python::{PythonFetch, PythonPreference, PythonRequest};
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::Workspace;
|
||||
use uv_workspace::{DiscoveryOptions, Workspace};
|
||||
|
||||
use crate::commands::pip::tree::DisplayDependencyGraph;
|
||||
use crate::commands::project::FoundInterpreter;
|
||||
|
|
@ -47,7 +47,8 @@ pub(crate) async fn tree(
|
|||
}
|
||||
|
||||
// Find the project requirements.
|
||||
let workspace = Workspace::discover(&std::env::current_dir()?, None).await?;
|
||||
let workspace =
|
||||
Workspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default()).await?;
|
||||
|
||||
// Find an interpreter for the project
|
||||
let interpreter = FoundInterpreter::discover(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use uv_python::{
|
|||
PYTHON_VERSION_FILENAME,
|
||||
};
|
||||
use uv_warnings::warn_user_once;
|
||||
use uv_workspace::VirtualProject;
|
||||
use uv_workspace::{DiscoveryOptions, VirtualProject};
|
||||
|
||||
use crate::commands::{project::find_requires_python, ExitStatus};
|
||||
use crate::printer::Printer;
|
||||
|
|
@ -33,7 +33,10 @@ pub(crate) async fn pin(
|
|||
warn_user_once!("`uv python pin` is experimental and may change without warning");
|
||||
}
|
||||
|
||||
let virtual_project = match VirtualProject::discover(&std::env::current_dir()?, None).await {
|
||||
let virtual_project =
|
||||
match VirtualProject::discover(&std::env::current_dir()?, &DiscoveryOptions::default())
|
||||
.await
|
||||
{
|
||||
Ok(virtual_project) if !isolated => Some(virtual_project),
|
||||
Ok(_) => None,
|
||||
Err(err) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Write;
|
||||
use std::io::stdout;
|
||||
|
|
@ -24,7 +23,7 @@ use uv_cli::{SelfCommand, SelfNamespace};
|
|||
use uv_configuration::Concurrency;
|
||||
use uv_requirements::RequirementsSource;
|
||||
use uv_settings::{Combine, FilesystemOptions};
|
||||
use uv_workspace::Workspace;
|
||||
use uv_workspace::{DiscoveryOptions, Workspace};
|
||||
|
||||
use crate::commands::{ExitStatus, ToolRunCommand};
|
||||
use crate::printer::Printer;
|
||||
|
|
@ -74,12 +73,14 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
Some(FilesystemOptions::from_file(config_file)?)
|
||||
} else if cli.global_args.isolated {
|
||||
None
|
||||
} else if let Ok(project) = Workspace::discover(&env::current_dir()?, None).await {
|
||||
} else if let Ok(project) =
|
||||
Workspace::discover(&std::env::current_dir()?, &DiscoveryOptions::default()).await
|
||||
{
|
||||
let project = FilesystemOptions::from_directory(project.install_path())?;
|
||||
let user = FilesystemOptions::user()?;
|
||||
project.combine(user)
|
||||
} else {
|
||||
let project = FilesystemOptions::find(env::current_dir()?)?;
|
||||
let project = FilesystemOptions::find(std::env::current_dir()?)?;
|
||||
let user = FilesystemOptions::user()?;
|
||||
project.combine(user)
|
||||
};
|
||||
|
|
@ -130,7 +131,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
.break_words(false)
|
||||
.word_separator(textwrap::WordSeparator::AsciiSpace)
|
||||
.word_splitter(textwrap::WordSplitter::NoHyphenation)
|
||||
.wrap_lines(env::var("UV_NO_WRAP").map(|_| false).unwrap_or(true))
|
||||
.wrap_lines(std::env::var("UV_NO_WRAP").map(|_| false).unwrap_or(true))
|
||||
.build(),
|
||||
)
|
||||
}))?;
|
||||
|
|
@ -834,8 +835,14 @@ async fn run_project(
|
|||
args.path,
|
||||
args.name,
|
||||
args.no_readme,
|
||||
args.python,
|
||||
globals.isolated,
|
||||
globals.preview,
|
||||
globals.python_preference,
|
||||
globals.python_fetch,
|
||||
globals.connectivity,
|
||||
globals.native_tls,
|
||||
&cache,
|
||||
printer,
|
||||
)
|
||||
.await
|
||||
|
|
@ -1100,7 +1107,7 @@ where
|
|||
// We support increasing the stack size to avoid stack overflows in debug mode on Windows. In
|
||||
// addition, we box types and futures in various places. This includes the `Box::pin(run())`
|
||||
// here, which prevents the large (non-send) main future alone from overflowing the stack.
|
||||
let result = if let Ok(stack_size) = env::var("UV_STACK_SIZE") {
|
||||
let result = if let Ok(stack_size) = std::env::var("UV_STACK_SIZE") {
|
||||
let stack_size = stack_size.parse().expect("Invalid stack size");
|
||||
let tokio_main = move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ pub(crate) struct InitSettings {
|
|||
pub(crate) path: Option<String>,
|
||||
pub(crate) name: Option<PackageName>,
|
||||
pub(crate) no_readme: bool,
|
||||
pub(crate) python: Option<String>,
|
||||
}
|
||||
|
||||
impl InitSettings {
|
||||
|
|
@ -164,12 +165,14 @@ impl InitSettings {
|
|||
path,
|
||||
name,
|
||||
no_readme,
|
||||
python,
|
||||
} = args;
|
||||
|
||||
Self {
|
||||
path,
|
||||
name,
|
||||
no_readme,
|
||||
python,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ fn init() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -65,7 +66,6 @@ fn init() -> Result<()> {
|
|||
----- stderr -----
|
||||
warning: `uv lock` is experimental and may change without warning
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
warning: No `requires-python` field found in the workspace. Defaulting to `>=3.12`.
|
||||
Resolved 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
|
|
@ -98,6 +98,7 @@ fn init_no_readme() -> Result<()> {
|
|||
name = "foo"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -140,6 +141,7 @@ fn current_dir() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -168,7 +170,6 @@ fn current_dir() -> Result<()> {
|
|||
----- stderr -----
|
||||
warning: `uv lock` is experimental and may change without warning
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
warning: No `requires-python` field found in the workspace. Defaulting to `>=3.12`.
|
||||
Resolved 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
|
|
@ -206,6 +207,7 @@ fn init_dot_args() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -234,7 +236,6 @@ fn init_dot_args() -> Result<()> {
|
|||
----- stderr -----
|
||||
warning: `uv lock` is experimental and may change without warning
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
warning: No `requires-python` field found in the workspace. Defaulting to `>=3.12`.
|
||||
Resolved 1 package in [TIME]
|
||||
"###);
|
||||
|
||||
|
|
@ -285,6 +286,7 @@ fn init_workspace() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -352,7 +354,6 @@ fn init_workspace_relative_sub_package() -> Result<()> {
|
|||
})?;
|
||||
|
||||
let child = context.temp_dir.join("foo");
|
||||
fs_err::create_dir(&child)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg("foo"), @r###"
|
||||
success: true
|
||||
|
|
@ -380,6 +381,7 @@ fn init_workspace_relative_sub_package() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -447,7 +449,6 @@ fn init_workspace_outside() -> Result<()> {
|
|||
})?;
|
||||
|
||||
let child = context.temp_dir.join("foo");
|
||||
fs_err::create_dir(&child)?;
|
||||
|
||||
// Run `uv init <path>` outside the workspace.
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.home_dir).arg(&child), @r###"
|
||||
|
|
@ -476,6 +477,7 @@ fn init_workspace_outside() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -556,6 +558,7 @@ fn init_invalid_names() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -690,6 +693,7 @@ fn init_nested_workspace() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
|
|
@ -804,8 +808,11 @@ fn init_matches_members() -> Result<()> {
|
|||
",
|
||||
})?;
|
||||
|
||||
// Create the parent directory (`packages`) and the child directory (`foo`), to ensure that
|
||||
// the empty child directory does _not_ trigger a workspace discovery error despite being a
|
||||
// valid member.
|
||||
let packages = context.temp_dir.join("packages");
|
||||
fs_err::create_dir_all(packages)?;
|
||||
fs_err::create_dir_all(packages.join("foo"))?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(context.temp_dir.join("packages")).arg("foo"), @r###"
|
||||
success: true
|
||||
|
|
@ -876,3 +883,163 @@ fn init_matches_exclude() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `uv init`, inheriting the `requires-python` from the workspace.
|
||||
#[test]
|
||||
fn init_requires_python_workspace() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = []
|
||||
"#,
|
||||
})?;
|
||||
|
||||
let child = context.temp_dir.join("foo");
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(&child), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv init` is experimental and may change without warning
|
||||
Adding `foo` as member of workspace `[TEMP_DIR]/`
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(child.join("pyproject.toml"))?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `uv init`, inferring the `requires-python` from the `--python` flag.
|
||||
#[test]
|
||||
fn init_requires_python_version() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = []
|
||||
"#,
|
||||
})?;
|
||||
|
||||
let child = context.temp_dir.join("foo");
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(&child).arg("--python").arg("3.8"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv init` is experimental and may change without warning
|
||||
Adding `foo` as member of workspace `[TEMP_DIR]/`
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(child.join("pyproject.toml"))?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run `uv init`, inferring the `requires-python` from the `--python` flag, and preserving the
|
||||
/// specifiers verbatim.
|
||||
#[test]
|
||||
fn init_requires_python_specifiers() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = []
|
||||
"#,
|
||||
})?;
|
||||
|
||||
let child = context.temp_dir.join("foo");
|
||||
uv_snapshot!(context.filters(), context.init().current_dir(&context.temp_dir).arg(&child).arg("--python").arg("==3.8.*"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv init` is experimental and may change without warning
|
||||
Adding `foo` as member of workspace `[TEMP_DIR]/`
|
||||
Initialized project `foo` at `[TEMP_DIR]/foo`
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(child.join("pyproject.toml"))?;
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
pyproject_toml, @r###"
|
||||
[project]
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = "==3.8.*"
|
||||
dependencies = []
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue