Skip to content

Commit

Permalink
feat: Add the project itself as an editable dependency (prefix-dev#1084)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim de Jager <[email protected]>
Co-authored-by: Tim de Jager <[email protected]>
  • Loading branch information
3 people authored Apr 4, 2024
1 parent ef13371 commit e89256e
Show file tree
Hide file tree
Showing 10 changed files with 835 additions and 44 deletions.
21 changes: 16 additions & 5 deletions docs/advanced/pyproject_toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"]
```
This is the minimum requirement for pixi to understand and parse the project.

If you use `pixi init` in a folder that has a `pyproject.toml` file, pixi will automatically add the `[tool.pixi.project]` section to the file.
And will also add some defaults to the `.gitignore` and `.gitattributes` file.
However, it is recommended you use `pixi init` in a folder that has a `pyproject.toml` file. Pixi will automatically

- Add the above `[tool.pixi.project]` section to the file, auto-detecting your current platform;
- Add the current project as an editable pypi dependency;
- Add some defaults to the `.gitignore` and `.gitattributes` file.

## Python dependency
The `pyproject.toml` file supports the `requires_python` field.
Expand Down Expand Up @@ -144,11 +147,19 @@ test = ["test"]
```

## Build-system section
The `pyproject.toml` file normally contains a `[build-system]` section.
Currently, pixi does not use this section, but it is recommended to keep it in the file for compatibility with other tools.
The `pyproject.toml` file normally contains a `[build-system]` section. Pixi will use this section to build and install the project if it is added as a pypi path dependency.

If the `pyproject.toml` file does not contain any `[build-system]` section, pixi will fall back to [uv](https://github.com/astral-sh/uv)'s default, which is equivalent to the below:

```toml title="pyproject.toml"
[build-system]
requires = ["setuptools >= 40.8.0"]
build-backend = "setuptools.build_meta:__legacy__"
```
Including a `[build-system]` section is **highly recommended**. If you are not sure of the [build-backend](https://packaging.python.org/en/latest/tutorials/packaging-projects/#choosing-build-backend) you want to use, including the `[build-system]` section below in your `pyproject.toml` is a good starting point

```toml title="pyproject.toml"
[build-system]
requires = ["setuptools", "wheel"]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
```
1 change: 1 addition & 0 deletions examples/flask-hello-world-pyproject/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.egg-info
Empty file.
File renamed without changes.
782 changes: 762 additions & 20 deletions examples/flask-hello-world-pyproject/pixi.lock

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions examples/flask-hello-world-pyproject/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@
name = "flask-hello-world-pyproject"
version = "0.1.0"
description = "Example how to get started with flask in a pixi environment."
license = "MIT OR Apache-2.0"
homepage = "https://github.com/prefix/pixi"
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["flask==2.*"]
dependencies = ["flask==2.*", "pytest"]

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[tool.pixi.project]
name = "flask-hello-world-pyproject"
channels = ["conda-forge"]
platforms = ["linux-64"]
platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"]

[tool.pixi.pypi-dependencies]
flask-hello-world-pyproject = { path = ".", editable = true }

[tool.pixi.tasks]
start = "python -m flask run --port=5050"
start = "python -m flask --app flask_hello_world_pyproject.app:app run --port=5050"
test = "pytest -v tests/*"
6 changes: 6 additions & 0 deletions examples/flask-hello-world-pyproject/tests/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import flask_hello_world_pyproject # noqa: F401


def test_import():
"""Simple test to validate the package itself has been installed."""
assert True
22 changes: 21 additions & 1 deletion src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use clap::Parser;
use indexmap::IndexMap;
use miette::IntoDiagnostic;
use minijinja::{context, Environment};
use pyproject_toml::PyProjectToml;
use rattler_conda_types::Platform;
use std::io::{Error, ErrorKind, Write};
use std::path::Path;
Expand Down Expand Up @@ -58,10 +59,14 @@ name = "{{ name }}"
channels = [{%- if channels %}"{{ channels|join("\", \"") }}"{%- endif %}]
platforms = ["{{ platforms|join("\", \"") }}"]
[tool.pixi.pypi-dependencies]
{{ name }} = { path = ".", editable = true }
"#;

const GITIGNORE_TEMPLATE: &str = r#"# pixi environments
.pixi
*.egg-info
"#;

Expand Down Expand Up @@ -147,12 +152,20 @@ pub async fn execute(args: Args) -> miette::Result<()> {
if pyproject_manifest_path.is_file() {
let file = fs::read_to_string(pyproject_manifest_path.clone()).unwrap();
if !file.contains("[tool.pixi.project]") {
// Get name from the pyproject [project] table
let name = match PyProjectToml::new(file.as_str()) {
Ok(pyproject) => pyproject
.project
.map(|p| p.name)
.expect("'name' should be defined in the [project] table"),
Err(e) => miette::bail!("Failed to parse 'pyproject.toml'. Error is {}", e),
};
let rv = env
.render_named_str(
consts::PYPROJECT_MANIFEST,
PYROJECT_TEMPLATE,
context! {
default_name,
name,
channels,
platforms
},
Expand All @@ -169,6 +182,13 @@ pub async fn execute(args: Args) -> miette::Result<()> {
pyproject_manifest_path.to_string_lossy(),
e
);
} else {
// Inform about the addition of the package itself as an editable dependency of the project
eprintln!(
"{}Added package '{}' as an editable dependency.",
console::style(console::Emoji("✔ ", "")).green(),
name
);
}
}

Expand Down
28 changes: 15 additions & 13 deletions src/project/manifest/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,31 @@ impl PyProjectManifest {

impl From<PyProjectManifest> for ProjectManifest {
fn from(item: PyProjectManifest) -> Self {
// Start by loading the data nested under "tool.pixi"
// Start by loading the data nested under "tool.pixi" as manifest,
// and create a reference to the 'pyproject.toml' project table
let mut manifest = item.tool.pixi.clone();
let pyproject = item
.project
.as_ref()
.expect("the [project] table should exist");

// TODO: tool.pixi.project.name should be made optional or read from project.name
// TODO: could copy across / convert some other optional fields if relevant

// Add python as dependency based on the project.requires_python property (if any)
let pythonspec = item
.project
.as_ref()
.and_then(|p| p.requires_python.as_ref())
.map(|v| VersionOrUrl::VersionSpecifier(v.clone()));
let pythonspec = pyproject
.requires_python
.clone()
.map(VersionOrUrl::VersionSpecifier);
let target = manifest.default_feature_mut().targets.default_mut();
target.add_dependency(
PackageName::from_str("python").unwrap(),
version_or_url_to_nameless_matchspec(&pythonspec).unwrap(),
SpecType::Run,
);

// add pyproject dependencies as pypi dependencies
if let Some(deps) = item
.project
.as_ref()
.and_then(|p| p.dependencies.as_ref())
.cloned()
{
// Add pyproject dependencies as pypi dependencies
if let Some(deps) = pyproject.dependencies.clone() {
for d in deps.into_iter() {
target.add_pypi_dependency(
PyPiPackageName::from_normalized(d.name.clone()),
Expand Down Expand Up @@ -115,6 +114,9 @@ mod tests {
};

const PYPROJECT_FULL: &str = r#"
[project]
name = "project"
[tool.pixi.project]
name = "project"
version = "0.1.0"
Expand Down
3 changes: 3 additions & 0 deletions tests/test_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ pixi run -v --manifest-path examples/pypi-source-deps/pixi.toml test
echo "Running the solve-groups example:"
pixi run -v --manifest-path examples/solve-groups/pixi.toml -e min-py38 test
pixi run -v --manifest-path examples/solve-groups/pixi.toml -e max-py310 test

echo "Running the flask-hello-world-pyproject example:"
pixi run -v --manifest-path examples/flask-hello-world-pyproject/pyproject.toml test

0 comments on commit e89256e

Please sign in to comment.