diff --git a/docs/build/python.md b/docs/build/python.md new file mode 100644 index 000000000..abc25ccd0 --- /dev/null +++ b/docs/build/python.md @@ -0,0 +1,107 @@ +# Tutorial: Doing Python development with Pixi + +In this tutorial, we will show you how to create a simple Python package with pixi. + +## Why is this useful? + +Pixi builds upon the conda ecosystem, which allows you to create a Python environment with all the dependencies you need. +Unlike PyPI, the conda ecosystem is cross-language and also offers packages written in Rust, R, C, C++ and many other languages. + +By building a Python package with pixi, you can: + +- build both conda and Python packages with the same tool +- manage Python packages and packages written in other languages in the same workspace + +## Let's get started + +First, we create a simple Python package with a `pyproject.toml` and a single Python file. +The package will be called `rich_example`, so we will create the following structure + +```shell +├── src # (1)! +│ └── rich_example +│ └── __init__.py +└── pyproject.toml +``` + +1. This project uses a src-layout, but pixi supports both [flat- and src-layouts](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/#src-layout-vs-flat-layout). + + +The Python package has a single function `main`. +Calling that, will print a table containing the name, age and city of three people. + +```py title="src/rich_example/__init__.py" +--8<-- "docs/source_files/pixi_projects/pixi_build_python/src/rich_example/__init__.py" +``` + + +The metadata of the Python package is defined in `pyproject.toml`. + +```toml title="pyproject.toml" +--8<-- "docs/source_files/pixi_projects/pixi_build_python/pyproject.toml" +``` + +1. We use the `rich` package to print the table in the terminal. +2. By specifying a script, the executable `rich-example-main` will be available in the environment. When being called it will in return call the `main` function of the `rich_example` module. +3. One can choose multiple backends to build a Python package, we choose `hatchling` which works well without additional configuration. + + +### Adding a `pixi.toml` + +What we have in the moment, constitutes a full Python package. +It could be uploaded to [PyPI](https://pypi.org/) as-is. + +However, we still need a tool to manage our environments and if we want other pixi projects to depend on our tool, we need to include more information. +We will do exactly that by creating a `pixi.toml`. + +!!! note + The pixi manifest can be in its own `pixi.toml` file or integrated in `pyproject.toml` + In this tutorial, we will use `pixi.toml`. + If you want everything integrated in `pyproject.toml` just copy the content of `pixi.toml` in this tutorial to your `pyproject.toml` and append `tool.pixi` to each table. + +The file structure will then look like this: + +```shell +├── src +│ └── rich_example +│ └── __init__.py +├── pixi.toml +└── pyproject.toml +``` + +This is the content of the `pixi.toml`: + +```toml title="pixi.toml" +--8<-- "docs/source_files/pixi_projects/pixi_build_python/pixi.toml" +``` + +1. In `workspace` information is set that is shared across all packages in the workspace. +2. In `dependencies` you specify all of your pixi packages. Here, this includes only our own package that is defined further below under `package` +3. We define a task that runs the `rich-example-main` executable we defined earlier. You can learn more about tasks in this [section](../features/advanced_tasks.md) +4. In `package` we define the actual pixi package. This information will be used when other pixi packages or workspaces depend on our package or when we upload it to a conda channel. +5. The same way, Python uses build backends to build a Python package, pixi uses build backends to build pixi packages. `pixi-build-python` creates a pixi package out of a Python package. +6. In `host-dependencies`, we add Python dependencies that are necessary to build the Python package. By adding them here as well, the dependencies will come from the conda channel rather than PyPI. +7. In `run-dependencies`, we add the Python dependencies needed during runtime. + + +When we now run `pixi run start`, we get the following output: + +``` +┏━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━┓ +┃ Name ┃ Age ┃ City ┃ +┡━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━━━━━┩ +│ John Doe │ 30 │ New York │ +│ Jane Smith │ 25 │ Los Angeles │ +│ Tim de Jager │ 35 │ Utrecht │ +└──────────────┴─────┴─────────────┘ +``` + +## Conclusion + +In this tutorial, we created a pixi package based on Python. +It can be used as-is, to upload to a conda channel or to PyPI. +In another tutorial we will learn how to add multiple pixi packages to the same workspace and let one pixi package use another. + +Thanks for reading! Happy Coding 🚀 + +Any questions? Feel free to reach out or share this tutorial on [X](https://twitter.com/prefix_dev), [join our Discord](https://discord.gg/kKV8ZxyzY4), send us an [e-mail](mailto:hi@prefix.dev) or follow our [GitHub](https://github.com/prefix-dev). diff --git a/docs/source_files/pixi_projects/pixi_build_python_example/.gitattributes b/docs/source_files/pixi_projects/pixi_build_python/.gitattributes similarity index 100% rename from docs/source_files/pixi_projects/pixi_build_python_example/.gitattributes rename to docs/source_files/pixi_projects/pixi_build_python/.gitattributes diff --git a/docs/source_files/pixi_projects/pixi_build_python_example/.gitignore b/docs/source_files/pixi_projects/pixi_build_python/.gitignore similarity index 100% rename from docs/source_files/pixi_projects/pixi_build_python_example/.gitignore rename to docs/source_files/pixi_projects/pixi_build_python/.gitignore diff --git a/docs/source_files/pixi_projects/pixi_build_python_example/pixi.lock b/docs/source_files/pixi_projects/pixi_build_python/pixi.lock similarity index 99% rename from docs/source_files/pixi_projects/pixi_build_python_example/pixi.lock rename to docs/source_files/pixi_projects/pixi_build_python/pixi.lock index 3fd29fd57..03f905d62 100644 --- a/docs/source_files/pixi_projects/pixi_build_python_example/pixi.lock +++ b/docs/source_files/pixi_projects/pixi_build_python/pixi.lock @@ -742,7 +742,7 @@ packages: size: 185646 timestamp: 1733342347277 - conda: . - name: rich-example + name: rich_example version: 0.1.0 build: pyhbf21a9e_0 subdir: noarch @@ -750,7 +750,7 @@ packages: - rich >=13.9.4,<14 - python input: - hash: 77f6f1205a5e91cda10b683f2e0e61f4c8cb0e890ab987111e9fe5c662ad7802 + hash: a0fa8e2205e41657fe6d9166741ec2d74c3c12776c2aa1fbdbb9236c21fbef90 globs: - pixi.toml - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda diff --git a/docs/source_files/pixi_projects/pixi_build_python_example/pixi.toml b/docs/source_files/pixi_projects/pixi_build_python/pixi.toml similarity index 53% rename from docs/source_files/pixi_projects/pixi_build_python_example/pixi.toml rename to docs/source_files/pixi_projects/pixi_build_python/pixi.toml index bd0ab43e0..5aeed3dba 100644 --- a/docs/source_files/pixi_projects/pixi_build_python_example/pixi.toml +++ b/docs/source_files/pixi_projects/pixi_build_python/pixi.toml @@ -1,31 +1,27 @@ -[workspace] +[workspace] # (1)! channels = ["https://prefix.dev/conda-forge"] platforms = ["win-64", "linux-64", "osx-arm64", "osx-64"] preview = ["pixi-build"] -[dependencies] +[dependencies] # (2)! rich_example = { path = "." } -[tasks] -test = "rich-example-main" +[tasks] # (3)! +start = "rich-example-main" -[package] +[package] # (4)! name = "rich_example" version = "0.1.0" -[build-system] +[build-system] # (5)! build-backend = { name = "pixi-build-python", version = "*" } channels = [ "https://prefix.dev/pixi-build-backends", "https://prefix.dev/conda-forge", ] -[host-dependencies] -# To be able to install this pyproject we need to install the dependencies of -# the python build-system defined above. Note that different from the -# pyproject build-system this refers to a conda package instead of a pypi -# package. +[host-dependencies] # (6)! hatchling = "==1.26.3" -[run-dependencies] +[run-dependencies] # (7)! rich = ">=13.9.4,<14" diff --git a/docs/source_files/pixi_projects/pixi_build_python/pyproject.toml b/docs/source_files/pixi_projects/pixi_build_python/pyproject.toml new file mode 100644 index 000000000..cf5e2985d --- /dev/null +++ b/docs/source_files/pixi_projects/pixi_build_python/pyproject.toml @@ -0,0 +1,10 @@ +[project] +dependencies = ["rich"] # (1)! +name = "rich_example" +requires-python = ">= 3.11" +scripts = { rich-example-main = "rich_example:main" } # (2)! +version = "0.1.0" + +[build-system] # (3)! +build-backend = "hatchling.build" +requires = ["hatchling"] diff --git a/docs/source_files/pixi_projects/pixi_build_python_example/src/rich_example/__init__.py b/docs/source_files/pixi_projects/pixi_build_python/src/rich_example/__init__.py similarity index 56% rename from docs/source_files/pixi_projects/pixi_build_python_example/src/rich_example/__init__.py rename to docs/source_files/pixi_projects/pixi_build_python/src/rich_example/__init__.py index be2121f01..a664c31cc 100644 --- a/docs/source_files/pixi_projects/pixi_build_python_example/src/rich_example/__init__.py +++ b/docs/source_files/pixi_projects/pixi_build_python/src/rich_example/__init__.py @@ -5,11 +5,11 @@ def main() -> None: console = Console() - table = Table(title="Simple Rich Example") + table = Table() - table.add_column("Name", justify="right", style="cyan", no_wrap=True) - table.add_column("Age", style="magenta") - table.add_column("City", justify="right", style="green") + table.add_column("Name") + table.add_column("Age") + table.add_column("City") table.add_row("John Doe", "30", "New York") table.add_row("Jane Smith", "25", "Los Angeles") diff --git a/docs/source_files/pixi_projects/pixi_build_python_example/pyproject.toml b/docs/source_files/pixi_projects/pixi_build_python_example/pyproject.toml deleted file mode 100644 index 077d950ce..000000000 --- a/docs/source_files/pixi_projects/pixi_build_python_example/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[project] -dependencies = ["rich"] -name = "rich_example" -requires-python = ">= 3.11" -version = "0.1.0" - -[project.scripts] -rich-example-main = "rich_example:main" - -[build-system] -build-backend = "hatchling.build" -requires = ["hatchling"] diff --git a/mkdocs.yml b/mkdocs.yml index 77d5b7d0e..8cc2b6af3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -126,6 +126,7 @@ nav: - Building Packages: - Getting started: build/getting_started.md - Dependency Types: build/dependency_types.md + - Building a Python package: build/python.md - Advanced: - Authentication: advanced/authentication.md - Channel Logic: advanced/channel_priority.md diff --git a/tests/integration_python/conftest.py b/tests/integration_python/conftest.py index d3988f05c..9f57b0491 100644 --- a/tests/integration_python/conftest.py +++ b/tests/integration_python/conftest.py @@ -77,3 +77,8 @@ def non_self_expose_channel_1(channels: Path) -> str: @pytest.fixture def non_self_expose_channel_2(channels: Path) -> str: return channels.joinpath("non_self_expose_channel_2").as_uri() + + +@pytest.fixture +def doc_pixi_projects() -> Path: + return Path(__file__).parents[2].joinpath("docs", "source_files", "pixi_projects") diff --git a/tests/integration_python/pixi_build/test_build.py b/tests/integration_python/pixi_build/test_build.py index a0dcc63d1..f2cea328e 100644 --- a/tests/integration_python/pixi_build/test_build.py +++ b/tests/integration_python/pixi_build/test_build.py @@ -5,16 +5,17 @@ from ..common import verify_cli_command -def test_build_conda_package(pixi: Path, examples_dir: Path, tmp_pixi_workspace: Path) -> None: +def test_build_conda_package(pixi: Path, tmp_pixi_workspace: Path, doc_pixi_projects: Path) -> None: """ This one tries to build the rich example project """ - pyproject = examples_dir / "rich_example" - target_dir = tmp_pixi_workspace / "pyproject" - shutil.copytree(pyproject, target_dir) + + project = doc_pixi_projects / "pixi_build_python" + target_dir = tmp_pixi_workspace / "project" + shutil.copytree(project, target_dir) shutil.rmtree(target_dir.joinpath(".pixi"), ignore_errors=True) - manifest_path = target_dir / "pyproject.toml" + manifest_path = target_dir / "pixi.toml" # build it verify_cli_command( diff --git a/tests/integration_python/test_docs.py b/tests/integration_python/test_docs.py index b28fec804..32aac46eb 100644 --- a/tests/integration_python/test_docs.py +++ b/tests/integration_python/test_docs.py @@ -6,17 +6,16 @@ @pytest.mark.slow -def test_doc_pixi_projects(pixi: Path, tmp_pixi_workspace: Path) -> None: +def test_doc_pixi_projects(pixi: Path, tmp_pixi_workspace: Path, doc_pixi_projects: Path) -> None: # TODO: Setting the cache dir shouldn't be necessary! env = {"PIXI_CACHE_DIR": str(tmp_pixi_workspace.joinpath("pixi_cache"))} - pixi_project_dir = Path(__file__).parents[2].joinpath("docs", "source_files", "pixi_projects") target_dir = tmp_pixi_workspace.joinpath("pixi_projects") - shutil.copytree(pixi_project_dir, target_dir) + shutil.copytree(doc_pixi_projects, target_dir) for pixi_project in target_dir.iterdir(): shutil.rmtree(pixi_project.joinpath(".pixi")) manifest = pixi_project.joinpath("pixi.toml") # Run the test command verify_cli_command( - [pixi, "run", "--manifest-path", manifest, "test"], ExitCode.SUCCESS, env=env + [pixi, "run", "--manifest-path", manifest, "start"], ExitCode.SUCCESS, env=env )