Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions docs/workflow_developers/update_conda_envs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,47 @@ Or use the ``pin-conda-envs`` command after updating the conda environment files
.. code:: console

$ snakedeploy pin-conda-envs workflow/envs/*.yaml

==============================================
Ensuring auxiliary package version constraints
==============================================

Use ``--ensure`` to require package versions to satisfy one or more Conda
version constraints. The constraints are checked against the resolved
environment. If the resulting environment does not satisfy them, the command
fails with an error.

Constraints use a package name
followed by a Conda version specifier; quote specs containing operators so
the shell does not interpret them. Specify the option once per constraint:

.. code:: console

$ snakedeploy pin-conda-envs workflow/envs/*.yaml --ensure 'python>=3.12' 'numpy>=2'

By default, pinning is updated whenever ``pin-conda-envs`` is run. Add
``--only-if-ensure-is-unsatisfied`` to skip re-pinning when an existing pin
already satisfies all supplied ``--ensure`` constraints. If a constraint is
unsatisfied (or there is no pin file to check), the environment is resolved
and pinned, and the constraints are checked again. The flag is also available
for ``update-conda-envs``; there, the environment is only updated if its
currently resolved versions do not satisfy the constraints. For example:

.. code:: console

$ snakedeploy pin-conda-envs workflow/envs/*.yaml --ensure 'python>=3.12' --only-if-ensure-is-unsatisfied
$ snakedeploy update-conda-envs workflow/envs/*.yaml --ensure 'python>=3.12' --only-if-ensure-is-unsatisfied

This can be helpful to e.g. ensure that all environments are safe.
For example, to ensure that all environment pins are safe with respect to the Python [emudhra issue](https://discuss.python.org/t/information-for-python-users-regarding-emudhra-emsign-ca-certificate-security-incident/109576), run:

.. code:: console

$ snakedeploy pin-conda-envs */*/environment.yaml */*/*/environment.yaml --ensure "certifi>=2026.10.19" "pip>=26.2.2" "uv>=0.13.4" "sigstore>=4.5.1" "pixi>=0.82.1" "conda>=26.9.4" --only-if-ensure-is-unsatisfied

If any environment pin does not satisfy the specified constraints, the command will fail with an error, indicating which constraints were not met.
You can then follow-up with ``snakedeploy update-conda-envs`` of the respective environment files, again using the same ``--ensure`` constraints, which will hopefully finally comply with the constraints.

.. note::

Use --ensure as the last argument to avoid confusion of the argument parser with position arguments like the environment files.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ publish = { features = ["publish"] }
[tool.pixi.feature.dev.tasks]
format = "ruff format"
check = "ruff check"
test = "/bin/bash tests/test_client.sh"
test = { cmd = "/bin/bash tests/test_client.sh && pytest -v", default-environment = "dev" }
build-docs = "sphinx-build -b html docs/ docs/_build/html"


Expand Down
69 changes: 45 additions & 24 deletions snakedeploy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,45 @@


def get_parser():
def add_ensure_args(subparser):
subparser.add_argument(
"--ensure",
nargs="+",
Comment thread
johanneskoester marked this conversation as resolved.
Resolved
metavar="PACKAGE_SPEC",
help="Ensure a package version constraint is satisfied after updating. "
"Ensured packages don't need to be present, just their version constraints need to be satisfied. "
"Can be specified multiple times (e.g. --ensure 'python>=3.12').",
)
subparser.add_argument(
"--only-if-ensure-is-unsatisfied",
action="store_true",
help="Only update if the ensure constraints are not satisfied.",
)

def add_create_pr_args(subparser, entity: str):
subparser.add_argument(
"--create-prs",
action="store_true",
help=f"Create pull request for each updated {entity}. "
"Requires GITHUB_TOKEN and GITHUB_REPOSITORY (the repo name) and one of GITHUB_REF_NAME or GITHUB_BASE_REF "
"(preferring the latter, representing the target branch, e.g. main or master) environment "
"variables to be set (the latter three are available when running as github action). "
"In order to enable further actions (e.g. checks) to run on the generated PRs, the "
"provided GITHUB_TOKEN may not be the default github actions token. See here for "
"options: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs.",
)
subparser.add_argument(
"--entity-regex",
help=f"Regular expression for deriving an entity name from the {entity} file name "
"(will be used for adding a label and for title and description). Has to contain a group 'entity' "
"(e.g. '(?P<entity>.+)/environment.yaml').",
)
subparser.add_argument(
"--pr-add-label",
action="store_true",
help="Add a label to the PR. Has to be used in combination with --entity-regex.",
)

parser = argparse.ArgumentParser(
description="Snakedeploy: deployment and maintenance related toolbox for Snakemake.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
Expand Down Expand Up @@ -146,6 +185,7 @@ def get_parser():
default="conda",
help="Conda frontend to use.",
)
add_ensure_args(pin_conda_envs)
pin_conda_envs.add_argument(
"--create-prs",
action="store_true",
Expand Down Expand Up @@ -174,30 +214,6 @@ def get_parser():
help="Only warn if conda env evaluation fails and go on with the other envs.",
)

def add_create_pr_args(subparser, entity: str):
subparser.add_argument(
"--create-prs",
action="store_true",
help=f"Create pull request for each updated {entity}. "
"Requires GITHUB_TOKEN and GITHUB_REPOSITORY (the repo name) and one of GITHUB_REF_NAME or GITHUB_BASE_REF "
"(preferring the latter, representing the target branch, e.g. main or master) environment "
"variables to be set (the latter three are available when running as github action). "
"In order to enable further actions (e.g. checks) to run on the generated PRs, the "
"provided GITHUB_TOKEN may not be the default github actions token. See here for "
"options: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs.",
)
subparser.add_argument(
"--entity-regex",
help=f"Regular expression for deriving an entity name from the {entity} file name "
"(will be used for adding a label and for title and description). Has to contain a group 'entity' "
"(e.g. '(?P<entity>.+)/environment.yaml').",
)
subparser.add_argument(
"--pr-add-label",
action="store_true",
help="Add a label to the PR. Has to be used in combination with --entity-regex.",
)

update_conda_envs = subparsers.add_parser(
"update-conda-envs",
help="Update given conda environment definition files (in YAML format) "
Expand All @@ -219,6 +235,7 @@ def add_create_pr_args(subparser, entity: str):
action="store_true",
help="also pin the updated environments (see pin-conda-envs subcommand).",
)
add_ensure_args(update_conda_envs)
add_create_pr_args(update_conda_envs, "environment")
update_conda_envs.add_argument(
"--warn-on-error",
Expand Down Expand Up @@ -316,6 +333,8 @@ def help(return_code=0):
entity_regex=args.entity_regex,
pr_add_label=args.pr_add_label,
warn_on_error=args.warn_on_error,
ensure=args.ensure,
only_if_ensure_is_unsatisfied=args.only_if_ensure_is_unsatisfied,
)
elif args.subcommand == "update-conda-envs":
update_conda_envs(
Expand All @@ -326,6 +345,8 @@ def help(return_code=0):
entity_regex=args.entity_regex,
pr_add_label=args.pr_add_label,
warn_on_error=args.warn_on_error,
ensure=args.ensure,
only_if_ensure_is_unsatisfied=args.only_if_ensure_is_unsatisfied,
)
elif args.subcommand == "update-snakemake-wrappers":
update_snakemake_wrappers(
Expand Down
Loading
Loading