Read package name from `pyproject.toml` in `uv run` (#3496)

## Summary

Right now, the project name is hard-coded.

Closes https://github.com/astral-sh/uv/issues/3491.
This commit is contained in:
Charlie Marsh 2024-05-09 15:42:53 -04:00 committed by GitHub
parent 51f4ab1c8d
commit 3dd34e218a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 72 additions and 16 deletions

View File

@ -1,19 +1,38 @@
use serde::Deserialize;
use std::path::{Path, PathBuf};
use tracing::debug;
use uv_fs::Simplified;
use uv_normalize::PackageName;
use uv_requirements::RequirementsSource;
#[derive(thiserror::Error, Debug)]
pub(crate) enum ProjectError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Toml(#[from] toml::de::Error),
#[error("No `project` section found in: {}", _0.user_display())]
MissingProject(PathBuf),
#[error("No `name` found in `project` section in: {}", _0.user_display())]
MissingName(PathBuf),
}
#[derive(Debug, Clone)]
pub(crate) struct Project {
/// The name of the package.
name: PackageName,
/// The path to the `pyproject.toml` file.
path: PathBuf,
}
impl Project {
/// Find the current project.
pub(crate) fn find(path: impl AsRef<Path>) -> Option<Self> {
pub(crate) fn find(path: impl AsRef<Path>) -> Result<Option<Self>, ProjectError> {
for ancestor in path.as_ref().ancestors() {
let pyproject_path = ancestor.join("pyproject.toml");
if pyproject_path.exists() {
@ -21,13 +40,27 @@ impl Project {
"Loading requirements from: {}",
pyproject_path.user_display()
);
return Some(Self {
// Read the `pyproject.toml`.
let contents = fs_err::read_to_string(&pyproject_path)?;
let pyproject_toml: PyProjectToml = toml::from_str(&contents)?;
// Extract the package name.
let Some(project) = pyproject_toml.project else {
return Err(ProjectError::MissingProject(pyproject_path));
};
let Some(name) = project.name else {
return Err(ProjectError::MissingName(pyproject_path));
};
return Ok(Some(Self {
name,
path: pyproject_path,
});
}));
}
}
None
Ok(None)
}
/// Return the requirements for the project.
@ -37,4 +70,22 @@ impl Project {
RequirementsSource::from_source_tree(self.path.parent().unwrap().to_path_buf()),
]
}
/// Return the [`PackageName`] for the project.
pub(crate) fn name(&self) -> &PackageName {
&self.name
}
}
/// A pyproject.toml as specified in PEP 517.
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
struct PyProjectToml {
project: Option<PyProjectProject>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
struct PyProjectProject {
name: Option<PackageName>,
}

View File

@ -33,7 +33,7 @@ pub(crate) async fn lock(
let venv = PythonEnvironment::from_virtualenv(cache)?;
// Find the project requirements.
let Some(project) = Project::find(std::env::current_dir()?) else {
let Some(project) = Project::find(std::env::current_dir()?)? else {
return Err(anyhow::anyhow!(
"Unable to find `pyproject.toml` for project."
));

View File

@ -62,7 +62,7 @@ pub(crate) async fn run(
} else {
debug!("Syncing project environment.");
let Some(project) = Project::find(std::env::current_dir()?) else {
let Some(project) = Project::find(std::env::current_dir()?)? else {
return Err(anyhow::anyhow!(
"Unable to find `pyproject.toml` for project."
));

View File

@ -8,11 +8,11 @@ use uv_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPySt
use uv_dispatch::BuildDispatch;
use uv_installer::SitePackages;
use uv_interpreter::PythonEnvironment;
use uv_normalize::PackageName;
use uv_resolver::{FlatIndex, InMemoryIndex, Lock};
use uv_types::{BuildIsolation, HashStrategy, InFlight};
use uv_warnings::warn_user;
use crate::commands::project::discovery::Project;
use crate::commands::{project, ExitStatus};
use crate::printer::Printer;
@ -32,6 +32,20 @@ pub(crate) async fn sync(
let markers = venv.interpreter().markers();
let tags = venv.interpreter().tags()?;
// Find the project requirements.
let Some(project) = Project::find(std::env::current_dir()?)? else {
return Err(anyhow::anyhow!(
"Unable to find `pyproject.toml` for project."
));
};
// Read the lockfile.
let resolution = {
let encoded = fs_err::tokio::read_to_string("uv.lock").await?;
let lock: Lock = toml::from_str(&encoded)?;
lock.to_resolution(markers, tags, project.name())
};
// Initialize the registry client.
// TODO(zanieb): Support client options e.g. offline, tls, etc.
let client = RegistryClientBuilder::new(cache.clone())
@ -69,15 +83,6 @@ pub(crate) async fn sync(
&no_binary,
);
// Read the lockfile.
let resolution = {
// TODO(charlie): Read the project name from disk.
let root = PackageName::new("project".to_string())?;
let encoded = fs_err::tokio::read_to_string("uv.lock").await?;
let lock: Lock = toml::from_str(&encoded)?;
lock.to_resolution(markers, tags, &root)
};
// Sync the environment.
project::install(
&resolution,