Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unified lockfile, pip interoperability #124

Merged
merged 62 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b5b13aa
Experimental support for pip dependencies
jvansanten Sep 27, 2021
532a807
Support pip dependencies from poetry
jvansanten Sep 27, 2021
b4fb601
Preliminary update support
jvansanten Oct 12, 2021
67e95e1
Enable update support for conda
jvansanten Oct 13, 2021
9ad0895
Get index info from tarballs
jvansanten Oct 18, 2021
bf39929
Allow exact versions
jvansanten Oct 18, 2021
de8849b
Allow platforms to be specified in pyproject.toml
jvansanten Oct 18, 2021
fecb7cc
Make poetry dependency optional
jvansanten Oct 20, 2021
f9cf342
Add multi-platform lockfile
jvansanten Nov 3, 2021
aace69d
Refactor Lockfile as pydantic model
jvansanten Nov 3, 2021
8f57c77
Simplify multi-platform solution
jvansanten Nov 4, 2021
5631a0a
Support osx-arm64
jvansanten Nov 4, 2021
79102a8
Handle non-JSON error output from micromamba
jvansanten Nov 4, 2021
03beb2e
Parse lockfile on render
jvansanten Nov 4, 2021
c8452f7
Parse lockfile on render
jvansanten Nov 10, 2021
d058f02
Ignore editable pip deps
jvansanten Nov 10, 2021
2a5425e
Merge remote-tracking branch 'upstream/main' into unified-lockfile
jvansanten Nov 11, 2021
8a7fbb0
Treat None as empty update
jvansanten Nov 12, 2021
323ec0c
Use Mapping for read-only dicts
jvansanten Nov 12, 2021
8c85671
Remove typing-extensions mapping
jvansanten Nov 12, 2021
63a8cf5
Explicitly check for conda manager
jvansanten Nov 12, 2021
07a61f8
Remove stray spec_hash() left over from testing
jvansanten Nov 12, 2021
445f8ee
Treat None as empty update
jvansanten Nov 12, 2021
a716ed7
ci: add poetry to requirements
jvansanten Nov 13, 2021
f1e866d
Add shim for functools.cache on py < 3.9
jvansanten Nov 15, 2021
3cc7806
Remove _get_repodata_for_package
jvansanten Nov 15, 2021
66c4e4e
Add update test
jvansanten Nov 15, 2021
86a479a
Mark update test as xfail with mamba
jvansanten Nov 15, 2021
53b9836
Restore pkgs_dirs support
jvansanten Nov 16, 2021
8de5e74
Add support for all manylinux tags
jvansanten Nov 16, 2021
f38e041
Pin non-target packages during update with mamba
jvansanten Nov 16, 2021
401e07c
Add docstrings to new and changed functions
jvansanten Nov 16, 2021
96415e3
Skip pip section if empty
jvansanten Nov 16, 2021
181e16b
fix: typo in _reconstruct_fetch_actions
jvansanten Nov 16, 2021
d654388
Properly handle micromamba LINK actions
jvansanten Nov 16, 2021
527733b
Back out overeager FETCH action overwrite for micromamba
jvansanten Nov 17, 2021
a989508
Send warnings to stderr
jvansanten Nov 17, 2021
0207db5
Raise exceptions from subprocesses instead of exiting
jvansanten Nov 18, 2021
47c75f4
Factor micromamba LINK action handling into separate block
jvansanten Nov 18, 2021
173d793
Correct error message from _do_validate_platform
jvansanten Nov 18, 2021
79a23c7
tests: check for PlatformValidationError
jvansanten Nov 18, 2021
b88215b
Wait for Popen to exit before checking returncode
jvansanten Nov 18, 2021
cc49fff
Increase test verbosity
jvansanten Nov 18, 2021
bd1d2d6
Use PurePosixPath to manipulate URLs
jvansanten Nov 18, 2021
dbbe4a0
tests: use ensureconda to find conda
jvansanten Nov 18, 2021
06f982f
tests: remove conda kwarg
jvansanten Nov 18, 2021
1b94d6a
Add required arg back to determine_conda_executable
jvansanten Nov 18, 2021
ae4f756
Lowercase name in is_micromamba()
jvansanten Nov 18, 2021
6ea3f1c
Support per-platform updates
jvansanten Nov 19, 2021
010c89e
Record paths of source files in locked metadata
jvansanten Nov 19, 2021
c37cae7
cleanup: remove unused imports
jvansanten Nov 19, 2021
8e326d1
Use version validation on install
jvansanten Nov 19, 2021
2abb8ff
Use native paths to find relative paths
jvansanten Nov 19, 2021
372c14f
Require source files to exist
jvansanten Nov 19, 2021
7dcc4de
tests: ensure fake source file exists
jvansanten Nov 19, 2021
f8041e2
Switch lockfile format from toml to yaml
jvansanten Nov 23, 2021
91ed7a4
Clarify relationship between LINK and FETCH
jvansanten Nov 23, 2021
831bca2
Add type annotations for parse_conda_requirement
jvansanten Nov 23, 2021
545cfdd
Document extensions to environment.yml
jvansanten Nov 23, 2021
1f5f224
Take a lockfile path from the command line
jvansanten Nov 24, 2021
df566dc
Use source file paths from lockfile by default
jvansanten Nov 24, 2021
9ac7af6
Add universal tags for all osx variants
jvansanten Nov 24, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jobs:
run: |
conda activate test
conda install mamba pip pytest-cov pytest-xdist
python -m pip install "ensureconda>=1.3"
python -m pip install -r requirements.txt
python -m pip install -r requirements-dev.txt

Expand Down Expand Up @@ -72,7 +71,6 @@ jobs:
echo "${PATH}"
which pip
which python
python -m pip install "ensureconda>=1.3"
python -m pip install -r requirements.txt
python -m pip install -r requirements-dev.txt

Expand All @@ -93,7 +91,7 @@ jobs:
ls -lah
set -x
which pytest
pytest -n auto --showlocals -vrsx --cov=conda_lock tests
pytest -n auto --showlocals -vvrsx --cov=conda_lock tests

- name: test-gdal
shell: bash -l {0}
Expand Down
111 changes: 105 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,40 @@ conda install -c conda-forge conda-lock
## Basic usage

```bash
# generate the lockfiles
# generate a multi-platform lockfile
conda-lock -f environment.yml -p osx-64 -p linux-64

# optionally, update the previous solution, using the latest version of
# pydantic that is compatible with the source specification
conda-lock --update pydantic

# create an environment from the lockfile
conda-lock install [-p {prefix}|-n {name}] conda-linux-64.lock
conda-lock install [-p {prefix}|-n {name}]

# alternatively, use conda command directly
# alternatively, render a single-platform lockfile and use conda command directly
conda-lock render -p linux-64
conda create -n my-locked-env --file conda-linux-64.lock
```

## Advanced usage

### File naming

By default conda-lock will name files as `"conda-{platform}.lock"`.
By default, `conda-lock` store its output in `conda-lock.yml` in the current
working directory. This file will also be used by default for render, install,
and update operations. You can supply a different filename with e.g.

```bash
conda-lock --lockfile superspecial.conda-lock.yml
```

The extension `.conda-lock.yml` will be added if not present. Rendered
environment files (env or explicit) will be named as as
`"conda-{platform}.lock"`.

If you want to override that call conda-lock as follows.
```bash
conda-lock --filename-template "specific-{platform}.conda.lock"
conda-lock -k explicit --filename-template "specific-{platform}.conda.lock"
```

### Compound specification
Expand All @@ -55,7 +70,7 @@ Conda-lock will build a spec list from several files if requested.
conda-lock -f base.yml -f specific.yml -p linux-64 --filename-template "specific-{platform}.lock"
````

In this case all dependencies are combined, and the first non-empty value for `channels` is used as the final
In this case all dependencies are combined, and the ordered union of all `channels` is used as the final
specification.

This works for all supported file types.
Expand All @@ -69,6 +84,52 @@ an [environment.yml][envyaml]
conda-lock -c conda-forge -p linux-64
```

#### platform specification

You may specify the platforms you wish to target by default directly in an [environment.yml][envyaml] using the (nonstandard) `platforms` key:

```yaml
# environment.yml
channels:
- conda-forge
dependencies:
- python=3.9
- pandas
platforms:
- osx-arm64
- linux-64
```

If you specify target platforms on the command line with `-p`, these will
override the values in the environment specification. If neither `platforms` nor
`-p` are provided, `conda-lock` will fall back to a default set of platforms.

#### default category

You can may wish to split your dependencies into separate files for better
organization, e.g. a `environment.yml` for production dependencies and a
`dev-environment.yml` for development dependencies. You can assign all the
dependencies parsed from a single file to a category using the (nonstandard)
`category` key.

```yaml
# dev-environment.yml
channels:
- conda-forge
dependencies:
- pytest
- mypy=0.910
category: dev
```

The default category is `main`.

### pip support

`conda-lock` can also lock the `dependencies.pip` section of
[environment.yml][envyaml], using [Poetry's][poetry] dependency solver, if
installed with the `pip_support` extra.

### --dev-dependencies/--no-dev-dependencies

By default conda-lock will include dev dependencies in the specification of the lock (if the files that the lock
Expand Down Expand Up @@ -194,6 +255,19 @@ channels = [
]
```

#### Platforms

Like in [environment.yml][envyaml], you can specify default platforms to target:

```toml
# pyproject.toml

[tool.conda-lock]
platforms = [
'osx-arm64', 'linux-64'
]
```

#### Extras

If your pyproject.toml file contains optional dependencies/extras these can be referred to by using the `--extras` flag
Expand Down Expand Up @@ -233,6 +307,30 @@ the following sections to the `pyproject.toml`
sqlite = ">=3.34"
```

#### pip dependencies

If a dependency refers directly to a URL rather than a package name and version,
`conda-lock` will assume it is pip-installable, e.g.:

```toml
# pyproject.toml
[tool.poetry.dependencies]
python = "3.9"
pymage = {url = "https://github.com/MickaelRigault/pymage/archive/v1.0.tar.gz#sha256=11e99c4ea06b76ca7fb5b42d1d35d64139a4fa6f7f163a2f0f9cc3ea0b3c55eb"}
```

Similarly, if a dependency is explicitly marked with `source = "pypi"`, it will
be treated as a `pip` dependency, e.g.:

```toml
[tool.poetry.dependencies]
python = "3.9"
ampel-ztf = {version = "^0.8.0-alpha.2", source = "pypi"}
```

In both these cases, the dependencies of `pip`-installable packages will also be
installed with `pip`, unless they were already requested by a `conda`
dependency.

## Dockerfile example

Expand Down Expand Up @@ -268,3 +366,4 @@ COPY --from=conda /opt/env /opt/env
[metayaml]: https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html
[mapping]: https://github.com/regro/cf-graph-countyfair/blob/master/mappings/pypi/grayskull_pypi_mapping.yaml
[envyaml]: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually
[poetry]: https://python-poetry.org
15 changes: 15 additions & 0 deletions conda-linux-64-true.lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Generated by conda-lock.
# platform: linux-64
# input_hash: 808732ae3e97a83923e4ea45884bad20e6b2569b55f5c4f1385d1f4c1566d78e

channels:
- conda-forge
- defaults
dependencies:
- _libgcc_mutex=0.1=conda_forge
- _openmp_mutex=4.5=1_gnu
- libgcc-ng=11.2.0=h1d223b6_11
- libgomp=11.2.0=h1d223b6_11
- libzlib=1.2.11=h36c2ea0_1013
- zlib=1.2.11=h36c2ea0_1013
- pip:
25 changes: 24 additions & 1 deletion conda_lock/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import json
import os
import pathlib

from typing import Dict
from itertools import chain
from typing import Dict, Iterable, List, TypeVar


T = TypeVar("T")


def get_in(keys, nested_dict, default=None):
Expand Down Expand Up @@ -33,3 +39,20 @@ def write_file(obj: str, filepath: str) -> None:
def read_json(filepath: str) -> Dict:
with open(filepath, mode="r") as fp:
return json.load(fp)


def ordered_union(collections: Iterable[Iterable[T]]) -> List[T]:
return list({k: k for k in chain.from_iterable(collections)}.values())


def relative_path(source: pathlib.Path, target: pathlib.Path) -> str:
"""
Get posix representation of the relative path from `source` to `target`.
Both `source` and `target` must exist on the filesystem.
"""
common = pathlib.PurePath(
os.path.commonpath((source.resolve(strict=True), target.resolve(strict=True)))
)
up = [".."] * len(source.resolve().relative_to(common).parents)
down = target.resolve().relative_to(common).parts
return str(pathlib.PurePosixPath(*up) / pathlib.PurePosixPath(*down))
Loading