diff --git a/py-rattler/docs/index.md b/py-rattler/docs/index.md index 5e732ade1..3e12a1d40 100644 --- a/py-rattler/docs/index.md +++ b/py-rattler/docs/index.md @@ -16,6 +16,17 @@ Rattler is written in Rust and tries to provide a clean API to its functionaliti With the primary goal in mind we aim to provide bindings to different languages to make it easy to integrate Rattler in non-rust projects. Py-rattler is the python bindings for rattler. +## Quick-Start + +Let's see an example to learn some of the functionality the library has to offer. + +```python +--8<-- "examples\solve_and_install.py" +``` + +Py-rattler provides friendly high level functions to download dependencies and create environments. +The `solve` and `install` functions are excellent examples of such high-level functions. + ## What is conda & conda-forge? The conda ecosystem provides **cross-platform**, **binary** packages that you can use with **any programming language**. @@ -65,79 +76,6 @@ $ conda install py-rattler $ mamba install py-rattler -c conda-forge ``` -## Quick-Start - -Let's see an example to learn some of the functionality the library has to offer. - -```python - -import asyncio - -from rattler import fetch_repo_data, solve, link, Channel, Platform, MatchSpec, VirtualPackage - -def download_callback(done, total): - print("", end = "\r") - print(f'{done/1024/1024:.2f}MiB/{total/1024/1024:.2f}MiB', end = "\r") - if done == total: - print() - -async def main(): - # channel to use to get the dependencies - channel = Channel("conda-forge") - - # list of dependencies to install in the env - match_specs = [ - MatchSpec("python ~=3.12.*"), - MatchSpec("pip"), - MatchSpec("requests 2.31.0") - ] - - # list of platforms to get the repo data - platforms = [Platform.current(), Platform("noarch")] - - virtual_packages = [p.into_generic() for p in VirtualPackage.current()] - - cache_path = "/tmp/py-rattler-cache/" - env_path = "/tmp/env-path/env" - - print("started fetching repo_data") - repo_data = await fetch_repo_data( - channels = [channel], - platforms = platforms, - cache_path = f"{cache_path}/repodata", - callback = download_callback, - ) - print("finished fetching repo_data") - - solved_dependencies = solve( - specs = match_specs, - available_packages = repo_data, - virtual_packages = virtual_packages, - ) - print("solved required dependencies") - - await link( - dependencies = solved_dependencies, - target_prefix = env_path, - cache_dir = f"{cache_path}/pkgs", - ) - print(f"created environment: {env_path}") - -if __name__ == "__main__": - asyncio.run(main()) - -``` - -Py-rattler provides friendly high level functions to download -dependencies and create environments. This is done through the -`fetch_repo_data`, `solve` and `link` functions. - -- `fetch_repo_data` as the name implies, fetches repo data from conda registries. -- `solve` function solves the requirements to get all the packages - which would be required to create the environment. -- `link` function takes a list of solved dependencies to create an - environment. - ## Next Steps These basic first steps should have gotten you started with the library. diff --git a/py-rattler/examples/solve_and_install.py b/py-rattler/examples/solve_and_install.py new file mode 100644 index 000000000..5cd7640c1 --- /dev/null +++ b/py-rattler/examples/solve_and_install.py @@ -0,0 +1,35 @@ +import asyncio +import tempfile + +from rattler import solve, install, VirtualPackage + + +async def main() -> None: + # Start by solving the environment. + # + # Solving is the process of going from specifications of package and their + # version requirements to a list of concrete packages. + print("started solving the environment") + solved_records = await solve( + # Channels to use for solving + channels=["conda-forge"], + # The specs to solve for + specs=["python ~=3.12.0", "pip", "requests 2.31.0"], + # Virtual packages define the specifications of the environment + virtual_packages=VirtualPackage.current(), + ) + print("solved required dependencies") + + # Install the packages into a new environment (or updates it if it already + # existed). + env_path = tempfile.mkdtemp() + await install( + records=solved_records, + target_prefix=env_path, + ) + + print(f"created environment: {env_path}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/py-rattler/mkdocs.yml b/py-rattler/mkdocs.yml index 1b71750a5..75e5e9ed7 100644 --- a/py-rattler/mkdocs.yml +++ b/py-rattler/mkdocs.yml @@ -84,6 +84,7 @@ markdown_extensions: toc_depth: 3 permalink: "#" - mdx_truly_sane_lists + - pymdownx.snippets nav: - First Steps: index.md @@ -91,7 +92,7 @@ nav: - core: - fetch: fetch_repo_data.md - solve: solver.md - - install: install.md + - install: installer.md - channel: - ChannelConfig: channel_config.md - Channel: channel.md diff --git a/py-rattler/pixi.toml b/py-rattler/pixi.toml index 7e2cdc766..2b2adfb16 100644 --- a/py-rattler/pixi.toml +++ b/py-rattler/pixi.toml @@ -41,7 +41,7 @@ types-networkx = "*" [feature.test.tasks] test = { cmd = "pytest --doctest-modules", depends_on = ["build"] } -fmt-python = "ruff format rattler" +fmt-python = "ruff format rattler examples" fmt-rust = "cargo fmt --all" lint-python = "ruff check ." lint-rust = "cargo clippy --all" @@ -51,7 +51,7 @@ type-check = { cmd = "mypy", depends_on = ["build"] } # checks for the CI fmt-rust-check = "cargo fmt --all --check" -fmt-python-check = "ruff format rattler --diff" +fmt-python-check = "ruff format rattler examples --diff" fmt-check = { depends_on = ["fmt-python-check", "fmt-rust-check"] } [feature.docs.dependencies] diff --git a/py-rattler/pyproject.toml b/py-rattler/pyproject.toml index 67591fe6d..7b9e68cba 100644 --- a/py-rattler/pyproject.toml +++ b/py-rattler/pyproject.toml @@ -37,7 +37,7 @@ target-version = "py38" [tool.mypy] python_version = "3.8" -files = ["rattler", "tests"] +files = ["rattler", "tests", "examples"] strict = true enable_error_code = ["redundant-expr", "truthy-bool", "ignore-without-code"] disable_error_code = ["empty-body"] diff --git a/py-rattler/rattler/install/installer.py b/py-rattler/rattler/install/installer.py index 35b37fb86..fa98ecde1 100644 --- a/py-rattler/rattler/install/installer.py +++ b/py-rattler/rattler/install/installer.py @@ -12,7 +12,7 @@ async def install( records: List[RepoDataRecord], - target_prefix: os.PathLike[str], + target_prefix: str | os.PathLike[str], cache_dir: Optional[os.PathLike[str]] = None, installed_packages: Optional[List[PrefixRecord]] = None, platform: Optional[Platform] = None, @@ -73,7 +73,7 @@ async def install( await py_install( records=records, - target_prefix=target_prefix, + target_prefix=str(target_prefix), cache_dir=cache_dir, installed_packages=installed_packages, platform=platform._inner if platform is not None else None, diff --git a/py-rattler/rattler/solver/solver.py b/py-rattler/rattler/solver/solver.py index dfd1204c5..015620297 100644 --- a/py-rattler/rattler/solver/solver.py +++ b/py-rattler/rattler/solver/solver.py @@ -1,8 +1,8 @@ from __future__ import annotations import datetime -from typing import List, Optional, Literal +from typing import List, Optional, Literal, Sequence -from rattler import Channel, Platform +from rattler import Channel, Platform, VirtualPackage from rattler.match_spec.match_spec import MatchSpec from rattler.channel import ChannelPriority @@ -18,13 +18,13 @@ async def solve( - channels: List[Channel | str], - specs: List[MatchSpec | str], + channels: Sequence[Channel | str], + specs: Sequence[MatchSpec | str], gateway: Gateway = Gateway(), - platforms: Optional[List[Platform | PlatformLiteral]] = None, - locked_packages: Optional[List[RepoDataRecord]] = None, - pinned_packages: Optional[List[RepoDataRecord]] = None, - virtual_packages: Optional[List[GenericVirtualPackage]] = None, + platforms: Optional[Sequence[Platform | PlatformLiteral]] = None, + locked_packages: Optional[Sequence[RepoDataRecord]] = None, + pinned_packages: Optional[Sequence[RepoDataRecord]] = None, + virtual_packages: Optional[Sequence[GenericVirtualPackage | VirtualPackage]] = None, timeout: Optional[datetime.timedelta] = None, channel_priority: ChannelPriority = ChannelPriority.Strict, exclude_newer: Optional[datetime.datetime] = None, @@ -94,7 +94,12 @@ async def solve( gateway=gateway._gateway, locked_packages=[package._record for package in locked_packages or []], pinned_packages=[package._record for package in pinned_packages or []], - virtual_packages=[v_package._generic_virtual_package for v_package in virtual_packages or []], + virtual_packages=[ + v_package.into_generic()._generic_virtual_package + if isinstance(v_package, VirtualPackage) + else v_package._generic_virtual_package + for v_package in virtual_packages or [] + ], channel_priority=channel_priority.value, timeout=int(timeout / datetime.timedelta(microseconds=1)) if timeout else None, exclude_newer_timestamp_ms=int(exclude_newer.replace(tzinfo=datetime.timezone.utc).timestamp() * 1000)