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
13 changes: 13 additions & 0 deletions conda_recipe_v2_schema/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,15 @@ class StagingRequirements(StrictBaseModel):
#########################


# Name of an optional dependency group (extras) as defined by CEP 44.
ExtrasGroupName = constr(pattern=r"^[a-z0-9_.+-]{1,64}$")

Extras = Annotated[
dict[ExtrasGroupName, ConditionalList[MatchSpec]],
Field(json_schema_extra={"additionalProperties": False}),
]


class Requirements(StrictBaseModel):
build: ConditionalList[MatchSpec] | None = Field(
None,
Expand All @@ -448,6 +457,10 @@ class Requirements(StrictBaseModel):
run_constraints: ConditionalList[MatchSpec] | None = Field(
None, description="constraints optional dependencies at runtime."
)
extras: Extras | None = Field(
None,
description="Optional dependency groups (CEP 44). Maps a group name to additional dependencies that are only installed when the group is selected via the `extras` MatchSpec keyword (e.g. `mypkg[extras=[group-name]]`).",
)
run_exports: ConditionalList[MatchSpec] | RunExports = Field(
None, description="The run exports of this package"
)
Expand Down
47 changes: 47 additions & 0 deletions examples/valid/extras/recipe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
context:
version: "0.0.15"
python_min: "3.13"

package:
name: pixi-browse
version: ${{ version }}

source:
url: https://pypi.org/packages/source/p/pixi-browse/pixi_browse-${{ version }}.tar.gz
sha256: 883f4f4e2ab8f07c8e04ebca8ad77a5bae82ba37cc085e85d6fa07d6871c8230

build:
number: 0
noarch: python
python:
entry_points:
- pixi-browse = pixi_browse.__main__:cli
script: python -m pip install . -vv --no-deps --no-build-isolation

requirements:
host:
- python ${{ python_min }}.*
- pip
- hatchling
run:
- python >=${{ python_min }}
- textual >=8.2.8,<9
- py-rattler >=0.26.0,<0.27
- typer >=0.27.2,<0.28
- pyyaml >=6.0.3,<7
run_constraints:
- textual-diff-view >=0.1.5,<0.2
extras:
diff:
- textual-diff-view >=0.1.5,<0.2

tests:
- python:
imports:
- pixi_browse

about:
homepage: https://github.com/pavelzw/pixi-browse
repository: https://github.com/pavelzw/pixi-browse
summary: A TUI to browse conda packages
license: BSD-3-Clause
39 changes: 39 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3773,6 +3773,45 @@
"description": "constraints optional dependencies at runtime.",
"title": "Run Constraints"
},
"extras": {
"anyOf": [
{
"additionalProperties": false,
"patternProperties": {
"^[a-z0-9_.+-]{1,64}$": {
"anyOf": [
{
"type": "string"
},
{
"$ref": "#/$defs/IfStatement"
},
{
"items": {
"anyOf": [
{
"type": "string"
},
{
"$ref": "#/$defs/IfStatement"
}
]
},
"type": "array"
}
]
}
},
"type": "object"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional dependency groups (CEP 44). Maps a group name to additional dependencies that are only installed when the group is selected via the `extras` MatchSpec keyword (e.g. `mypkg[extras=[group-name]]`).",
"title": "Extras"
},
"run_exports": {
"anyOf": [
{
Expand Down
45 changes: 44 additions & 1 deletion tests/test_recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@pytest.fixture(
scope="module",
params=["mamba", "xtensor", "single-output", "zlib", "staging", "post-process"],
params=["mamba", "xtensor", "single-output", "zlib", "staging", "post-process", "extras"],
)
def valid_recipe(request) -> str:
recipe_name = request.param
Expand Down Expand Up @@ -57,6 +57,49 @@ def test_recipe_schema_not_changed(recipe_schema):
assert recipe_schema == top_level_schema()


def test_requirements_extras_valid(recipe_schema):
"""Recipes with requirements.extras pass validation (CEP 44)."""
recipe_yaml = """
package:
name: test
version: 1.0.0
requirements:
run:
- main-dependency
extras:
diff:
- textual-diff-view >=0.1.5,<0.2
"full-extra.v2":
- extra-dependency >=2
- if: unix
then: posix-only-dependency
"""
recipe_dict = yaml.safe_load(recipe_yaml)

Recipe.validate_python(recipe_dict)
validate(instance=recipe_dict, schema=recipe_schema)


def test_requirements_extras_invalid_group_name(recipe_schema):
"""Extras group names must match the CEP 44 grammar `[a-z0-9_.+-]{1,64}`."""
recipe_yaml = """
package:
name: test
version: 1.0.0
requirements:
extras:
Invalid_Name:
- extra-dependency
"""
recipe_dict = yaml.safe_load(recipe_yaml)

with pytest.raises(PydanticValidationError):
Recipe.validate_python(recipe_dict)

with pytest.raises(ValidationError):
validate(instance=recipe_dict, schema=recipe_schema)


def test_patches_valid_conditional():
"""Test that patches with conditionals inside a list pass validation."""
recipe_yaml = """
Expand Down