From 502fb389570b82ce5caa0a2ea73af8e8e5957147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 10 Apr 2026 17:32:29 +0200 Subject: [PATCH 01/18] Remove unused `serialize` / `deserialize` model methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/models/test_variant.py | 54 ------------------------------------ variantlib/models/variant.py | 19 ------------- 2 files changed, 73 deletions(-) diff --git a/tests/models/test_variant.py b/tests/models/test_variant.py index f0812e58..912ef4cd 100644 --- a/tests/models/test_variant.py +++ b/tests/models/test_variant.py @@ -177,29 +177,6 @@ def test_from_str_invalid_format() -> None: VariantProperty.from_str(input_str) -def test_variantprop_serialization() -> None: - vprop = VariantProperty(namespace="provider", feature="feature", value="value") - assert vprop.serialize() == { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - - -def test_variantprop_deserialization() -> None: - data = { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - - vprop = VariantProperty.deserialize(data) - - assert vprop.namespace == data["namespace"] - assert vprop.feature == data["feature"] - assert vprop.value == data["value"] - - def test_variantprop_sorting() -> None: data = [ VariantProperty("z", "a", "a"), @@ -348,37 +325,6 @@ def test_variantdescription_hexdigest_adjacent_strings() -> None: ) -def test_variantdescription_serialization() -> None: - vprop = VariantProperty(namespace="provider", feature="feature", value="value") - vdesc = VariantDescription(properties=[vprop]) - - assert vdesc.serialize() == [ - { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - ] - - -def test_variantdescription_deserialization() -> None: - data = [ - { - "namespace": "provider", - "feature": "feature", - "value": "value", - } - ] - - vdesc = VariantDescription.deserialize(data) - - assert len(vdesc.properties) == 1 - assert vdesc.properties[0].namespace == "provider" - assert vdesc.properties[0].feature == "feature" - assert vdesc.properties[0].value == "value" - assert vdesc.hexdigest == "c44d3adf" - - # ----------------------------------------------- # Fuzzy Testing # ----------------------------------------------- diff --git a/variantlib/models/variant.py b/variantlib/models/variant.py index ed79e9e6..9fc6d360 100644 --- a/variantlib/models/variant.py +++ b/variantlib/models/variant.py @@ -69,16 +69,6 @@ def to_str(self) -> str: # Variant-Property: :: :: return f"{self.namespace} :: {self.feature}" - def serialize(self) -> dict[str, str]: - return asdict(self) - - @classmethod - def deserialize(cls, data: dict[str, str]) -> Self: - for field_name in cls.__dataclass_fields__: - if field_name not in data: - raise ValidationError(f"Extra field not known: `{field_name}`") - return cls(**data) - @classmethod def from_str(cls, input_str: str) -> Self: # Try matching the input string with the regex pattern @@ -213,15 +203,6 @@ def hexdigest(self) -> str: # Source: https://docs.python.org/3/library/hashlib.html#hashlib.hash.hexdigest return hash_object.hexdigest()[:VARIANT_HASH_LENGTH] - @classmethod - def deserialize(cls, properties: list[dict[str, str]]) -> Self: - return cls( - properties=[VariantProperty.deserialize(vdata) for vdata in properties] - ) - - def serialize(self) -> list[dict[str, str]]: - return [vprop.serialize() for vprop in self.properties] - def to_dict(self) -> VariantInfoJsonDict: data = asdict(self) From 2abd8ff146061f66251e9c2c004cf33f6cd2f5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 10 Apr 2026 17:41:36 +0200 Subject: [PATCH 02/18] Add a (not used yet) label field to `VariantDescription` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/models/variant.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/variantlib/models/variant.py b/variantlib/models/variant.py index 9fc6d360..53586af8 100644 --- a/variantlib/models/variant.py +++ b/variantlib/models/variant.py @@ -9,11 +9,13 @@ from dataclasses import field from functools import cached_property +from variantlib.constants import NULL_VARIANT_LABEL from variantlib.constants import VALIDATION_FEATURE_NAME_REGEX from variantlib.constants import VALIDATION_FEATURE_REGEX from variantlib.constants import VALIDATION_NAMESPACE_REGEX from variantlib.constants import VALIDATION_PROPERTY_REGEX from variantlib.constants import VALIDATION_VALUE_REGEX +from variantlib.constants import VALIDATION_VARIANT_LABEL_REGEX from variantlib.constants import VariantInfoJsonDict from variantlib.errors import ValidationError from variantlib.models.base import BaseModel @@ -160,6 +162,19 @@ class VariantDescription(BaseModel): default_factory=list, ) + label: str = field( + metadata={ + "validator": lambda val: validate_and( + [ + lambda v: validate_type(v, str), + lambda v: validate_matches_re(v, VALIDATION_VARIANT_LABEL_REGEX), # pyright: ignore[reportArgumentType] + ], + value=val, + ), + }, + default=NULL_VARIANT_LABEL, + ) + def __post_init__(self) -> None: # We sort the data so that they always get displayed/hashed # in a consistent manner. @@ -172,6 +187,19 @@ def __post_init__(self) -> None: # Execute the validator super().__post_init__() + # TODO: enable once we're done porting + if False: + if self.is_null_variant(): + if self.label != NULL_VARIANT_LABEL: + raise ValidationError( + f"Null variant must always use {NULL_VARIANT_LABEL!r} label" + ) + elif self.label == NULL_VARIANT_LABEL: + raise ValidationError( + f"{NULL_VARIANT_LABEL!r} label can be used only for the null " + "variant" + ) + def is_null_variant(self) -> bool: """ Check if the variant is a null variant. From 5f133d9fdf25b3f5a36bb188097d64c8085f5ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 10 Apr 2026 17:56:11 +0200 Subject: [PATCH 03/18] Populate label in `from_dict()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/test_variant_dist_info.py | 14 +++++++------- tests/test_variants_json.py | 15 ++++++++++++--- variantlib/models/variant.py | 4 ++-- variantlib/variants_json.py | 4 +++- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/tests/test_variant_dist_info.py b/tests/test_variant_dist_info.py index 1a9c2814..cd5c5845 100644 --- a/tests/test_variant_dist_info.py +++ b/tests/test_variant_dist_info.py @@ -26,12 +26,12 @@ VARIANT_INFO_PROVIDER_DATA_KEY: { "ns": {VARIANT_INFO_PROVIDER_REQUIRES_KEY: ["ns-pkg"]} }, - VARIANTS_JSON_VARIANT_DATA_KEY: {"bdbc6ca0": {"ns": {"f": ["v"]}}}, + VARIANTS_JSON_VARIANT_DATA_KEY: {"test": {"ns": {"f": ["v"]}}}, } @pytest.mark.parametrize("json_type", [str, bytes]) -@pytest.mark.parametrize("expected_label", [None, "bdbc6ca0"]) +@pytest.mark.parametrize("expected_label", [None, "test"]) def test_variant_dist_info(json_type: type, expected_label: str | None) -> None: vjson_str = ( json.dumps(VARIANT_JSON) @@ -43,21 +43,21 @@ def test_variant_dist_info(json_type: type, expected_label: str | None) -> None: assert variant_dist_info.feature_priorities == {} assert variant_dist_info.property_priorities == {} assert variant_dist_info.providers == {"ns": ProviderInfo(requires=["ns-pkg"])} - vdesc = VariantDescription([VariantProperty("ns", "f", "v")]) - assert variant_dist_info.variants == {vdesc.hexdigest: vdesc} + vdesc = VariantDescription([VariantProperty("ns", "f", "v")], label="test") + assert variant_dist_info.variants == {"test": vdesc} assert variant_dist_info.variant_desc == vdesc - assert variant_dist_info.variant_label == vdesc.hexdigest + assert variant_dist_info.variant_label == "test" @pytest.mark.parametrize("expected_label", [None, "fancy1"]) def test_variant_dist_info_custom_label(expected_label: str | None) -> None: - vjson_str = json.dumps(VARIANT_JSON).replace("bdbc6ca0", "fancy1") + vjson_str = json.dumps(VARIANT_JSON).replace("test", "fancy1") variant_dist_info = VariantDistInfo(vjson_str, expected_label=expected_label) assert variant_dist_info.namespace_priorities == ["ns"] assert variant_dist_info.feature_priorities == {} assert variant_dist_info.property_priorities == {} assert variant_dist_info.providers == {"ns": ProviderInfo(requires=["ns-pkg"])} - vdesc = VariantDescription([VariantProperty("ns", "f", "v")]) + vdesc = VariantDescription([VariantProperty("ns", "f", "v")], label="fancy1") assert variant_dist_info.variants == {"fancy1": vdesc} assert variant_dist_info.variant_desc == vdesc assert variant_dist_info.variant_label == "fancy1" diff --git a/tests/test_variants_json.py b/tests/test_variants_json.py index 4805c9fa..a8bc9822 100644 --- a/tests/test_variants_json.py +++ b/tests/test_variants_json.py @@ -46,6 +46,7 @@ def test_validate_variants_json() -> None: assert variants_json.variants == { NULL_VARIANT_LABEL: VariantDescription(), "03e04d5e": VariantDescription( + label="03e04d5e", properties=[ VariantProperty( namespace="fictional_hw", @@ -60,6 +61,7 @@ def test_validate_variants_json() -> None: ], ), "36028aca": VariantDescription( + label="36028aca", properties=[ VariantProperty( namespace="fictional_hw", @@ -89,6 +91,7 @@ def test_validate_variants_json() -> None: ], ), "3f7188c1": VariantDescription( + label="3f7188c1", properties=[ VariantProperty( namespace="fictional_hw", @@ -113,6 +116,7 @@ def test_validate_variants_json() -> None: ], ), "7db6d39f": VariantDescription( + label="7db6d39f", properties=[ VariantProperty( namespace="fictional_tech", @@ -132,6 +136,7 @@ def test_validate_variants_json() -> None: ], ), "808c7f9d": VariantDescription( + label="808c7f9d", properties=[ VariantProperty( namespace="fictional_tech", @@ -151,6 +156,7 @@ def test_validate_variants_json() -> None: ], ), "80fa16ff": VariantDescription( + label="80fa16ff", properties=[ VariantProperty( namespace="fictional_hw", @@ -175,6 +181,7 @@ def test_validate_variants_json() -> None: ], ), "3351fc6a": VariantDescription( + label="3351fc6a", properties=[ VariantProperty( namespace="fictional_hw", @@ -182,9 +189,10 @@ def test_validate_variants_json() -> None: value=str(value), ) for value in range(4, 6) - ] + ], ), "181830db": VariantDescription( + label="181830db", properties=[ VariantProperty( namespace="fictional_hw", @@ -192,9 +200,10 @@ def test_validate_variants_json() -> None: value=str(value), ) for value in range(4, 8) - ] + ], ), "72c47fce": VariantDescription( + label="72c47fce", properties=[ VariantProperty( namespace="fictional_hw", @@ -206,7 +215,7 @@ def test_validate_variants_json() -> None: feature="compute_capability", value="8", ), - ] + ], ), } assert variants_json.namespace_priorities == ["fictional_hw", "fictional_tech"] diff --git a/variantlib/models/variant.py b/variantlib/models/variant.py index 53586af8..ea443c13 100644 --- a/variantlib/models/variant.py +++ b/variantlib/models/variant.py @@ -247,7 +247,7 @@ def to_dict(self) -> VariantInfoJsonDict: return dict(result) @classmethod - def from_dict(cls, data: VariantInfoJsonDict) -> Self: + def from_dict(cls, data: VariantInfoJsonDict, label: str) -> Self: vprops = [ VariantProperty(namespace=namespace, feature=key, value=vprop_val) for namespace, vdata in data.items() @@ -255,7 +255,7 @@ def from_dict(cls, data: VariantInfoJsonDict) -> Self: for vprop_val in vprop_values ] - return cls(vprops) + return cls(properties=vprops, label=label) @dataclass(frozen=True) diff --git a/variantlib/variants_json.py b/variantlib/variants_json.py index 363eeb5f..325956a1 100644 --- a/variantlib/variants_json.py +++ b/variantlib/variants_json.py @@ -179,7 +179,9 @@ def _process(self, variant_table: VariantsJsonDict) -> None: VariantInfoJsonDict, ignore_subkeys=True, ) as packed_vdesc: - vdesc = VariantDescription.from_dict(packed_vdesc) + vdesc = VariantDescription.from_dict( + packed_vdesc, label=variant_label + ) if vdesc.is_null_variant() and variant_label != NULL_VARIANT_LABEL: raise ValidationError( f"Null variant must use {NULL_VARIANT_LABEL!r} label" From f0e76317ab99ca8879c3853399bdc3a2c3870089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 10 Apr 2026 18:14:47 +0200 Subject: [PATCH 04/18] Add fallback sort on variant label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note that we do not guarantee passing variant label to the function yet. Signed-off-by: Michał Górny --- tests/resolver/test_sorting.py | 64 ++++++++++++++++++++++++++++++++++ variantlib/resolver/sorting.py | 5 +-- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/tests/resolver/test_sorting.py b/tests/resolver/test_sorting.py index 3966aebb..c3bcd40b 100644 --- a/tests/resolver/test_sorting.py +++ b/tests/resolver/test_sorting.py @@ -366,3 +366,67 @@ def test_sort_variants_descriptions_validation_error( vdescs=vdescs, property_priorities=property_priorities, ) + + +def test_sort_variants_descriptions_by_label() -> None: + vprops_proprioty_list = [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ] + + vdesc1a = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + ], + label="a", + ) + vdesc1b = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + ], + label="b", + ) + vdesc1c = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_a", value="value"), + ], + label="c", + ) + + vdesc2a = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ], + label="a", + ) + vdesc2b = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ], + label="b", + ) + vdesc2c = VariantDescription( + [ + VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), + ], + label="c", + ) + + assert sort_variants_descriptions( + vdescs=[vdesc1a, vdesc1b, vdesc1c], property_priorities=vprops_proprioty_list + ) == [vdesc1a, vdesc1b, vdesc1c] + assert sort_variants_descriptions( + vdescs=[vdesc1b, vdesc1c, vdesc1a], property_priorities=vprops_proprioty_list + ) == [vdesc1a, vdesc1b, vdesc1c] + assert sort_variants_descriptions( + vdescs=[vdesc1c, vdesc1b, vdesc1a], property_priorities=vprops_proprioty_list + ) == [vdesc1a, vdesc1b, vdesc1c] + + assert sort_variants_descriptions( + vdescs=[vdesc1a, vdesc1b, vdesc1c, vdesc2a, vdesc2b], + property_priorities=vprops_proprioty_list, + ) == [vdesc1a, vdesc1b, vdesc1c, vdesc2a, vdesc2b] + assert sort_variants_descriptions( + vdescs=[vdesc2a, vdesc2c, vdesc1a, vdesc1b], + property_priorities=vprops_proprioty_list, + ) == [vdesc1a, vdesc1b, vdesc2a, vdesc2c] diff --git a/variantlib/resolver/sorting.py b/variantlib/resolver/sorting.py index 1de41d8c..a4ddf5a7 100644 --- a/variantlib/resolver/sorting.py +++ b/variantlib/resolver/sorting.py @@ -195,7 +195,7 @@ def sort_variants_descriptions( property_lookup_table = dict(property_lookup_table) lookup_table_size = len(property_lookup_table) - def _get_rank_tuple(vdesc: VariantDescription) -> tuple[int, ...]: + def _get_rank_tuple(vdesc: VariantDescription) -> tuple[tuple[int, ...], str]: """ Get the rank tuple of a `VariantDescription` object. @@ -232,6 +232,7 @@ def _get_rank_tuple(vdesc: VariantDescription) -> tuple[int, ...]: if any(ranking_array[idx] == sys.maxsize for idx in vdesc_feature_indexes): raise ValidationError("Filtering should be applied first.") - return tuple(ranking_array) + # As a fallback, sort on variant label. + return (tuple(ranking_array), vdesc.label) return sorted(vdescs, key=_get_rank_tuple) From bdb34f77e5ef1bc5caa9b304ed918d3e86d4e7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 10 Apr 2026 19:01:58 +0200 Subject: [PATCH 05/18] Update variant model tests for labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/models/test_variant.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/tests/models/test_variant.py b/tests/models/test_variant.py index 912ef4cd..3485418d 100644 --- a/tests/models/test_variant.py +++ b/tests/models/test_variant.py @@ -6,9 +6,11 @@ import pytest from hypothesis import given from hypothesis import strategies as st +from variantlib.constants import NULL_VARIANT_LABEL from variantlib.constants import VALIDATION_FEATURE_NAME_REGEX from variantlib.constants import VALIDATION_NAMESPACE_REGEX from variantlib.constants import VALIDATION_VALUE_REGEX +from variantlib.constants import VALIDATION_VARIANT_LABEL_REGEX from variantlib.errors import ValidationError from variantlib.models.variant import VARIANT_HASH_LENGTH from variantlib.models.variant import VariantDescription @@ -214,6 +216,7 @@ def test_null_variant() -> None: vdesc = VariantDescription() assert vdesc.properties == [] assert vdesc.hexdigest == hashlib.sha256(b"").hexdigest()[:VARIANT_HASH_LENGTH] + assert vdesc.label == NULL_VARIANT_LABEL def test_variantdescription_initialization() -> None: @@ -224,7 +227,8 @@ def test_variantdescription_initialization() -> None: vprop2 = VariantProperty( namespace="tyrell_corporation", feature="client_id", value="secret_pass" ) - vdesc = VariantDescription([vprop1, vprop2]) + vdesc = VariantDescription([vprop1, vprop2], label="test") + assert vdesc.label == "test" # Check that the _data property is a list assert isinstance(vdesc.properties, list) @@ -257,14 +261,13 @@ def test_variantdescription_duplicate_data() -> None: def test_variantdescription_partial_duplicate_data() -> None: - # Test that duplicate VariantProperty instances are removed vprop1 = VariantProperty( namespace="omnicorp", feature="custom_feat", value="secret_value" ) vprop2 = VariantProperty( namespace="omnicorp", feature="custom_feat", value="another_value" ) - VariantDescription([vprop1, vprop2]) + VariantDescription([vprop1, vprop2], label="test") def test_variantdescription_sorted_data() -> None: @@ -278,7 +281,7 @@ def test_variantdescription_sorted_data() -> None: vprop3 = VariantProperty( namespace="omnicorp", feature="secret_pass", value="client_value" ) - vdesc = VariantDescription([vprop1, vprop2, vprop3]) + vdesc = VariantDescription([vprop1, vprop2, vprop3], label="test") # Check that data is sorted by namespace, feature, and value sorted_vprops = sorted( @@ -296,7 +299,7 @@ def test_variantdescription_hexdigest() -> None: namespace="tyrell_corporation", feature="client_id", value="secret_pass" ) vprops = [vprop1, vprop2] - vdesc = VariantDescription(vprops) + vdesc = VariantDescription(vprops, label="test") # Compute the expected hash using shake_128 (mock the hash output for testing) hash_object = hashlib.sha256( @@ -314,13 +317,15 @@ def test_variantdescription_hexdigest_adjacent_strings() -> None: [ VariantProperty("a", "b", "cx"), VariantProperty("d", "e", "f"), - ] + ], + label="test", ).hexdigest != VariantDescription( [ VariantProperty("a", "b", "c"), VariantProperty("xd", "e", "f"), - ] + ], + label="another", ).hexdigest ) @@ -390,7 +395,7 @@ def test_fuzzy_variantprop(namespace: str, feature: str, value: str) -> None: ) def test_fuzzy_variantdescription(vprop: list[VariantProperty]) -> None: # Fuzzy test for random combinations of VariantDescription - vdesc = VariantDescription(vprop) + vdesc = VariantDescription(vprop, label="test") assert isinstance(vdesc.properties, list) assert len(vdesc.properties) >= 1 @@ -414,8 +419,23 @@ def test_fuzzy_variantdescription(vprop: list[VariantProperty]) -> None: value=st.from_regex(VALIDATION_NAMESPACE_REGEX, fullmatch=True), ), ), + label=st.from_regex(VALIDATION_VARIANT_LABEL_REGEX, fullmatch=True), ) ) def test_random_hexdigest(vdesc: VariantDescription) -> None: assert isinstance(vdesc.hexdigest, str) assert len(vdesc.hexdigest) == VARIANT_HASH_LENGTH + + +@pytest.mark.xfail(reason="Validation is disabled for porting") +def test_null_variant_label(): + with pytest.raises( + ValidationError, + match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", + ): + VariantDescription([VariantProperty("a", "b", "c")], label=NULL_VARIANT_LABEL) + with pytest.raises( + ValidationError, + match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label", + ): + VariantDescription(label="zuul") From 54593fef6ef05007184ec05c3830ed0b75ebd2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 10 Apr 2026 19:31:20 +0200 Subject: [PATCH 06/18] Update resolver tests to define labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/resolver/test_filtering.py | 22 ++-- tests/resolver/test_lib.py | 194 +++++++++++++++---------------- tests/resolver/test_sorting.py | 22 ++-- 3 files changed, 123 insertions(+), 115 deletions(-) diff --git a/tests/resolver/test_filtering.py b/tests/resolver/test_filtering.py index b6efe8c7..32838184 100644 --- a/tests/resolver/test_filtering.py +++ b/tests/resolver/test_filtering.py @@ -32,9 +32,9 @@ def vdescs(vprops: list[VariantProperty]) -> list[VariantDescription]: vprop1, vprop2 = vprops return [ - VariantDescription([vprop1]), - VariantDescription([vprop2]), - VariantDescription([vprop1, vprop2]), + VariantDescription([vprop1], label="a"), + VariantDescription([vprop2], label="b"), + VariantDescription([vprop1, vprop2], label="c"), ] @@ -139,11 +139,11 @@ def test_filter_variants_by_namespaces(vdescs: list[VariantDescription]) -> None ("vdescs", "forbidden_namespaces"), [ ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], [VariantProperty("not", "a", "str")], ), ("not a list", ["omnicorp"]), @@ -245,11 +245,11 @@ def test_filter_variants_by_features( ("vdescs", "forbidden_features"), [ ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], ["not a `VariantFeature`"], ), ("not a list", VariantFeature("a", "b")), @@ -453,22 +453,22 @@ def test_filter_variants_by_property( [VariantProperty("a", "b", "c")], ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", [VariantProperty("a", "b", "c")], ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], ["not a `VariantFeature`"], [VariantProperty("a", "b", "c")], ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], [VariantProperty("a", "b", "c")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], [VariantProperty("a", "b", "c")], ["not a `VariantFeature`"], ), diff --git a/tests/resolver/test_lib.py b/tests/resolver/test_lib.py index 2d32449b..3a5656d6 100644 --- a/tests/resolver/test_lib.py +++ b/tests/resolver/test_lib.py @@ -100,82 +100,82 @@ def vdescs(vprops: list[VariantProperty]) -> list[VariantDescription]: # Important: vprop4 and vprop5 are mutually exclusive return [ # variants with 5 properties - VariantDescription([vprop1, vprop2, vprop3, vprop4, vprop6]), - VariantDescription([vprop1, vprop2, vprop3, vprop5, vprop6]), + VariantDescription([vprop1, vprop2, vprop3, vprop4, vprop6], label="a"), + VariantDescription([vprop1, vprop2, vprop3, vprop5, vprop6], label="b"), # variants with 4 properties - VariantDescription([vprop1, vprop2, vprop3, vprop4]), # - vprop6 - VariantDescription([vprop1, vprop2, vprop3, vprop5]), # - vprop6 + VariantDescription([vprop1, vprop2, vprop3, vprop4], label="c"), # - vprop6 + VariantDescription([vprop1, vprop2, vprop3, vprop5], label="d"), # - vprop6 - VariantDescription([vprop1, vprop2, vprop3, vprop6]), # - vprop4/5 + VariantDescription([vprop1, vprop2, vprop3, vprop6], label="c"), # - vprop4/5 - VariantDescription([vprop1, vprop2, vprop4, vprop6]), # - vprop3 - VariantDescription([vprop1, vprop2, vprop5, vprop6]), # - vprop3 + VariantDescription([vprop1, vprop2, vprop4, vprop6], label="d"), # - vprop3 + VariantDescription([vprop1, vprop2, vprop5, vprop6], label="e"), # - vprop3 - VariantDescription([vprop1, vprop3, vprop4, vprop6]), # - vprop2 - VariantDescription([vprop1, vprop3, vprop5, vprop6]), # - vprop2 + VariantDescription([vprop1, vprop3, vprop4, vprop6], label="f"), # - vprop2 + VariantDescription([vprop1, vprop3, vprop5, vprop6], label="g"), # - vprop2 - VariantDescription([vprop2, vprop3, vprop5, vprop6]), # - vprop1 - VariantDescription([vprop2, vprop3, vprop5, vprop6]), # - vprop1 + VariantDescription([vprop2, vprop3, vprop5, vprop6], label="h"), # - vprop1 + VariantDescription([vprop2, vprop3, vprop5, vprop6], label="i"), # - vprop1 # variants with 3 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop2, vprop3]), - VariantDescription([vprop1, vprop2, vprop4]), - VariantDescription([vprop1, vprop2, vprop5]), - VariantDescription([vprop1, vprop2, vprop6]), + VariantDescription([vprop1, vprop2, vprop3], label="j"), + VariantDescription([vprop1, vprop2, vprop4], label="k"), + VariantDescription([vprop1, vprop2, vprop5], label="l"), + VariantDescription([vprop1, vprop2, vprop6], label="m"), - VariantDescription([vprop1, vprop3, vprop4]), - VariantDescription([vprop1, vprop3, vprop5]), - VariantDescription([vprop1, vprop3, vprop6]), + VariantDescription([vprop1, vprop3, vprop4], label="n"), + VariantDescription([vprop1, vprop3, vprop5], label="o"), + VariantDescription([vprop1, vprop3, vprop6], label="p"), - VariantDescription([vprop1, vprop4, vprop6]), - VariantDescription([vprop1, vprop5, vprop6]), + VariantDescription([vprop1, vprop4, vprop6], label="q"), + VariantDescription([vprop1, vprop5, vprop6], label="r"), # --- vprop2 --- # - VariantDescription([vprop2, vprop3, vprop4]), - VariantDescription([vprop2, vprop3, vprop5]), - VariantDescription([vprop2, vprop3, vprop6]), + VariantDescription([vprop2, vprop3, vprop4], label="s"), + VariantDescription([vprop2, vprop3, vprop5], label="t"), + VariantDescription([vprop2, vprop3, vprop6], label="u"), - VariantDescription([vprop2, vprop4, vprop6]), - VariantDescription([vprop2, vprop5, vprop6]), + VariantDescription([vprop2, vprop4, vprop6], label="v"), + VariantDescription([vprop2, vprop5, vprop6], label="w"), # --- vprop3 --- # - VariantDescription([vprop3, vprop4, vprop6]), - VariantDescription([vprop3, vprop5, vprop6]), + VariantDescription([vprop3, vprop4, vprop6], label="x"), + VariantDescription([vprop3, vprop5, vprop6], label="y"), # variants with 2 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop2]), - VariantDescription([vprop1, vprop3]), - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop1, vprop5]), - VariantDescription([vprop1, vprop6]), + VariantDescription([vprop1, vprop2], label="z"), + VariantDescription([vprop1, vprop3], label="aa"), + VariantDescription([vprop1, vprop4], label="ab"), + VariantDescription([vprop1, vprop5], label="ac"), + VariantDescription([vprop1, vprop6], label="ad"), # --- vprop2 --- # - VariantDescription([vprop2, vprop3]), - VariantDescription([vprop2, vprop4]), - VariantDescription([vprop2, vprop5]), - VariantDescription([vprop2, vprop6]), + VariantDescription([vprop2, vprop3], label="ae"), + VariantDescription([vprop2, vprop4], label="af"), + VariantDescription([vprop2, vprop5], label="ag"), + VariantDescription([vprop2, vprop6], label="ah"), # --- vprop3 --- # - VariantDescription([vprop3, vprop4]), - VariantDescription([vprop3, vprop5]), - VariantDescription([vprop3, vprop6]), + VariantDescription([vprop3, vprop4], label="ai"), + VariantDescription([vprop3, vprop5], label="aj"), + VariantDescription([vprop3, vprop6], label="ak"), # --- vprop4 --- # - VariantDescription([vprop4, vprop6]), + VariantDescription([vprop4, vprop6], label="al"), # --- vprop5 --- # - VariantDescription([vprop5, vprop6]), + VariantDescription([vprop5, vprop6], label="am"), # variants with 1 property - VariantDescription([vprop1]), - VariantDescription([vprop2]), - VariantDescription([vprop3]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), - VariantDescription([vprop6]), + VariantDescription([vprop1], label="an"), + VariantDescription([vprop2], label="ao"), + VariantDescription([vprop3], label="ap"), + VariantDescription([vprop4], label="aq"), + VariantDescription([vprop5], label="ar"), + VariantDescription([vprop6], label="as"), ] # fmt: on @@ -207,7 +207,7 @@ def test_filter_variants_only_one_prop_allowed( vdescs=inputs_vdescs, allowed_properties=[vprop4], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] assert ( list( @@ -258,7 +258,7 @@ def test_filter_variants_forbidden_feature_allowed_prop( allowed_properties=[vprop4], forbidden_features=[vprop2.feature_object], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] def test_filter_variants_forbidden_namespace_allowed_prop( @@ -276,7 +276,7 @@ def test_filter_variants_forbidden_namespace_allowed_prop( allowed_properties=[vprop4], forbidden_namespaces=["NotExisting"], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] assert list( filter_variants( @@ -284,7 +284,7 @@ def test_filter_variants_forbidden_namespace_allowed_prop( allowed_properties=[vprop4], forbidden_namespaces=[vprop1.namespace], ) - ) == [VariantDescription([vprop4])] + ) == [VariantDescription([vprop4], label="aq")] def test_filter_variants_only_remove_duplicates( @@ -320,22 +320,22 @@ def test_filter_variants_remove_duplicates_and_namespaces( expected_vdescs = [ # --- vprop3 --- # - VariantDescription([vprop3, vprop4, vprop6]), - VariantDescription([vprop3, vprop5, vprop6]), + VariantDescription([vprop3, vprop4, vprop6], label="test"), + VariantDescription([vprop3, vprop5, vprop6], label="test"), # variants with 2 properties # --- vprop3 --- # - VariantDescription([vprop3, vprop4]), - VariantDescription([vprop3, vprop5]), - VariantDescription([vprop3, vprop6]), + VariantDescription([vprop3, vprop4], label="test"), + VariantDescription([vprop3, vprop5], label="test"), + VariantDescription([vprop3, vprop6], label="test"), # --- vprop4 --- # - VariantDescription([vprop4, vprop6]), + VariantDescription([vprop4, vprop6], label="test"), # --- vprop5 --- # - VariantDescription([vprop5, vprop6]), + VariantDescription([vprop5, vprop6], label="test"), # variants with 1 property - VariantDescription([vprop3]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), - VariantDescription([vprop6]), + VariantDescription([vprop3], label="test"), + VariantDescription([vprop4], label="test"), + VariantDescription([vprop5], label="test"), + VariantDescription([vprop6], label="test"), # Null-Variant is never removed and last VariantDescription(), ] @@ -379,12 +379,12 @@ def test_filter_variants_remove_duplicates_and_features( expected_vdescs = [ # variants with 2 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop1, vprop5]), + VariantDescription([vprop1, vprop4], label="test"), + VariantDescription([vprop1, vprop5], label="test"), # variants with 1 property - VariantDescription([vprop1]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), + VariantDescription([vprop1], label="test"), + VariantDescription([vprop4], label="test"), + VariantDescription([vprop5], label="test"), # Null-Variant is never removed and last VariantDescription(), ] @@ -434,12 +434,12 @@ def test_filter_variants_remove_duplicates_and_properties( expected_vdescs = [ # variants with 2 properties # --- vprop1 --- # - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop1, vprop5]), + VariantDescription([vprop1, vprop4], label="test"), + VariantDescription([vprop1, vprop5], label="test"), # variants with 1 property - VariantDescription([vprop1]), - VariantDescription([vprop4]), - VariantDescription([vprop5]), + VariantDescription([vprop1], label="test"), + VariantDescription([vprop4], label="test"), + VariantDescription([vprop5], label="test"), # Null-Variant is never removed and last VariantDescription(), ] @@ -520,48 +520,48 @@ def test_sort_and_filter_supported_variants( # 1. Everything with vprop6 # 1.1. + vprop3 # 1.1.1. + vprop5 - VariantDescription([vprop1, vprop3, vprop5, vprop6]), - VariantDescription([vprop3, vprop5, vprop6]), + VariantDescription([vprop1, vprop3, vprop5, vprop6], label="g"), + VariantDescription([vprop3, vprop5, vprop6], label="y"), # 1.1.2. + vprop4 - VariantDescription([vprop1, vprop3, vprop4, vprop6]), - VariantDescription([vprop3, vprop4, vprop6]), + VariantDescription([vprop1, vprop3, vprop4, vprop6], label="f"), + VariantDescription([vprop3, vprop4, vprop6], label="x"), # 1.1.3. + vprop1 - VariantDescription([vprop1, vprop3, vprop6]), + VariantDescription([vprop1, vprop3, vprop6], label="p"), # 1.1.4. vprop6 + vprop3 - VariantDescription([vprop3, vprop6]), + VariantDescription([vprop3, vprop6], label="ak"), # 1.2. + vprop5 - VariantDescription([vprop1, vprop5, vprop6]), - VariantDescription([vprop5, vprop6]), + VariantDescription([vprop1, vprop5, vprop6], label="r"), + VariantDescription([vprop5, vprop6], label="am"), # 1.3. + vprop4 - VariantDescription([vprop1, vprop4, vprop6]), - VariantDescription([vprop4, vprop6]), + VariantDescription([vprop1, vprop4, vprop6], label="q"), + VariantDescription([vprop4, vprop6], label="al"), # 1.4. + vprop1 - VariantDescription([vprop1, vprop6]), + VariantDescription([vprop1, vprop6], label="ad"), # 1. sole vprop6 - VariantDescription([vprop6]), + VariantDescription([vprop6], label="as"), # 2. Everything with vprop3 # 2.1. + vprop5 - VariantDescription([vprop1, vprop3, vprop5]), - VariantDescription([vprop3, vprop5]), + VariantDescription([vprop1, vprop3, vprop5], label="o"), + VariantDescription([vprop3, vprop5], label="aj"), # 2.2. + vprop4 - VariantDescription([vprop1, vprop3, vprop4]), - VariantDescription([vprop3, vprop4]), + VariantDescription([vprop1, vprop3, vprop4], label="n"), + VariantDescription([vprop3, vprop4], label="ai"), # 2.3. + vprop1 - VariantDescription([vprop1, vprop3]), + VariantDescription([vprop1, vprop3], label="aa"), # 2. sole vprop3 - VariantDescription([vprop3]), + VariantDescription([vprop3], label="ap"), # 3. vprop5 - VariantDescription([vprop1, vprop5]), - VariantDescription([vprop5]), + VariantDescription([vprop1, vprop5], label="ac"), + VariantDescription([vprop5], label="ar"), # 4. vprop4 - VariantDescription([vprop1, vprop4]), - VariantDescription([vprop4]), + VariantDescription([vprop1, vprop4], label="ab"), + VariantDescription([vprop4], label="aq"), # 5. sole vprop1 - VariantDescription([vprop1]), + VariantDescription([vprop1], label="an"), # Null-Variant is never removed and last - Implicitly added VariantDescription(), @@ -590,11 +590,11 @@ def test_sort_and_filter_supported_variants( ("vdescs", "feature_priorities"), [ ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], "not a list", ), ( - [VariantDescription([VariantProperty("a", "b", "c")])], + [VariantDescription([VariantProperty("a", "b", "c")], label="test")], {"a": [VariantFeature("not_a", "variantproperty")]}, ), ("not a list", {"a": ["a"]}), diff --git a/tests/resolver/test_sorting.py b/tests/resolver/test_sorting.py index c3bcd40b..d730e352 100644 --- a/tests/resolver/test_sorting.py +++ b/tests/resolver/test_sorting.py @@ -239,13 +239,16 @@ def test_sort_variants_descriptions() -> None: VariantProperty(namespace="omnicorp", feature="feat_b", value="value1"), VariantProperty(namespace="omnicorp", feature="feat_c", value="value"), VariantProperty(namespace="other_corp", feature="feat_c", value="value"), - ] + ], + label="a", ) vdesc2 = VariantDescription( - [VariantProperty(namespace="other_corp", feature="feat_a", value="value")] + [VariantProperty(namespace="other_corp", feature="feat_a", value="value")], + label="b", ) vdesc3 = VariantDescription( - [VariantProperty(namespace="omnicorp", feature="feat_a", value="value")] + [VariantProperty(namespace="omnicorp", feature="feat_a", value="value")], + label="c", ) assert sort_variants_descriptions( @@ -317,7 +320,8 @@ def test_sort_variants_descriptions() -> None: VariantProperty( namespace="omnicorp", feature="feat", value="other_value" ) - ] + ], + label="a", ), VariantDescription( properties=[ @@ -326,6 +330,7 @@ def test_sort_variants_descriptions() -> None: namespace="omnicorp", feature="other_feat", value="other_value" ), ], + label="b", ), ], ) @@ -347,13 +352,16 @@ def test_sort_variants_descriptions_ranking_validation_error( [ ("not a list", [VariantProperty("a", "b", "c")]), (["not a VariantDescription"], [VariantProperty("a", "b", "c")]), - (VariantDescription([VariantProperty("a", "b", "c")]), "not a list or None"), ( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="test"), + "not a list or None", + ), + ( + VariantDescription([VariantProperty("a", "b", "c")], label="test"), VariantProperty("not", "a", "list"), ), ( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="test"), [{"not a VariantProperty": True}], ), ], From 98d9cba062684d11c10d6d932f2c4ae263bed6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 10 Apr 2026 20:19:39 +0200 Subject: [PATCH 07/18] Update more tests to pass variant label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/test_api.py | 94 ++++++++++++++++------------------- tests/test_utils.py | 117 ++++++++++++++++++++++---------------------- tests/utils.py | 4 +- 3 files changed, 102 insertions(+), 113 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index d6ed1d35..dab8da74 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,7 +1,6 @@ from __future__ import annotations import json -import re import string from collections.abc import Generator from typing import TYPE_CHECKING @@ -336,7 +335,7 @@ def test_validate_variant(optional: bool) -> None: # Verify whether the variant properties are valid res = validate_variant( - VariantDescription(list(expected.keys())), + VariantDescription(list(expected.keys()), label="test"), variant_info=variant_info, ) @@ -357,10 +356,10 @@ def test_validate_variant(optional: bool) -> None: @pytest.mark.parametrize( "pyproject_toml", [None, PYPROJECT_TOML, PYPROJECT_TOML_MINIMAL] ) -@pytest.mark.parametrize("label", [None, "foo", "xy1.2"]) +@pytest.mark.parametrize("label", ["foo", "xy1.2"]) def test_make_variant_dist_info( pyproject_toml: VariantsJsonDict | None, - label: str | None, + label: str, ) -> None: expected: VariantsJsonDict = { VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, @@ -441,7 +440,8 @@ def test_make_variant_dist_info( VariantProperty("ns1", "f1", "p1"), VariantProperty("ns1", "f2", "p2"), VariantProperty("ns2", "f1", "p1"), - ] + ], + label=label, ), variant_info=VariantPyProjectToml(pyproject_toml) # type: ignore[arg-type] if pyproject_toml is not None @@ -478,7 +478,8 @@ def common_variant_info() -> VariantInfo: [ VariantProperty("test_namespace", "name2", "val2c"), VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="test", ), True, ), @@ -486,7 +487,8 @@ def common_variant_info() -> VariantInfo: VariantDescription( [ VariantProperty("test_namespace", "name1", "val1c"), - ] + ], + label="test", ), False, ), @@ -526,27 +528,30 @@ def test_check_variant_supported_generic() -> None: [ VariantProperty("test_namespace", "name2", "val2c"), VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="test", ), variant_info=variant_info, ) # test an usupported variant assert not check_variant_supported( - vdesc=VariantDescription([VariantProperty("test_namespace", "name1", "val1c")]), + vdesc=VariantDescription( + [VariantProperty("test_namespace", "name1", "val1c")], label="test" + ), variant_info=variant_info, ) -@pytest.mark.parametrize("label", [None, "foo"]) -def test_get_variant_environment_dict(label: str | None) -> None: +def test_get_variant_environment_dict() -> None: vdesc = VariantDescription( [ VariantProperty("ns1", "feat1", "val1"), VariantProperty("ns1", "feat2", "val2"), VariantProperty("ns2", "feat1", "val1"), VariantProperty("ns3", "feat2", "val2"), - ] + ], + label="foo", ) expected: dict[str, set[str] | str] = { "variant_features": { @@ -566,10 +571,9 @@ def test_get_variant_environment_dict(label: str | None) -> None: "ns2 :: feat1 :: val1", "ns3 :: feat2 :: val2", }, + "variant_label": "foo", } - if label is not None: - expected["variant_label"] = label - assert get_variant_environment_dict(vdesc, label) == expected + assert get_variant_environment_dict(vdesc, "foo") == expected def test_make_variant_dist_info_invalid_label(): @@ -583,18 +587,17 @@ def test_make_variant_dist_info_invalid_label(): match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", ): make_variant_dist_info( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription( + [VariantProperty("a", "b", "c")], label=NULL_VARIANT_LABEL + ), variant_label=NULL_VARIANT_LABEL, ) with pytest.raises( ValidationError, - match=re.escape( - "Invalid variant label: 'foo/bar' " - "(must consist only of alphanumeric characters, underscores and dots)" - ), + match=("Value `foo/bar` must match regex"), ): make_variant_dist_info( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="foo/bar"), variant_label="foo/bar", ) @@ -606,35 +609,17 @@ def test_get_variant_label() -> None: == NULL_VARIANT_LABEL ) - assert ( - get_variant_label(VariantDescription([VariantProperty("a", "b", "c")])) - == "01a9783a" - ) - assert ( - get_variant_label( - VariantDescription( - [VariantProperty("a", "b", "c"), VariantProperty("d", "e", "f")] - ) - ) - == "eb9a66a7" - ) assert ( get_variant_label( - VariantDescription( - [VariantProperty("d", "e", "f"), VariantProperty("a", "b", "c")] - ) + VariantDescription([VariantProperty("a", "b", "c")], label="foo"), "foo" ) - == "eb9a66a7" - ) - - assert ( - get_variant_label(VariantDescription([VariantProperty("a", "b", "c")]), "foo") == "foo" ) assert ( get_variant_label( VariantDescription( - [VariantProperty("a", "b", "c"), VariantProperty("d", "e", "f")] + [VariantProperty("a", "b", "c"), VariantProperty("d", "e", "f")], + label="foo", ), "foo", ) @@ -645,24 +630,23 @@ def test_get_variant_label() -> None: ValidationError, match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label", ): - get_variant_label(VariantDescription([]), "foo") + get_variant_label(VariantDescription([], label="foo"), "foo") with pytest.raises( ValidationError, match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", ): get_variant_label( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription( + [VariantProperty("a", "b", "c")], label=NULL_VARIANT_LABEL + ), NULL_VARIANT_LABEL, ) with pytest.raises( ValidationError, - match=re.escape( - "Invalid variant label: 'foo/bar' " - "(must consist only of alphanumeric characters, underscores and dots)" - ), + match=("Value `foo/bar` must match regex"), ): get_variant_label( - VariantDescription([VariantProperty("a", "b", "c")]), + VariantDescription([VariantProperty("a", "b", "c")], label="foo/bar"), "foo/bar", ) @@ -677,7 +661,8 @@ def test_make_variant_dist_info_expand_aot_plugin_properties( vdesc = VariantDescription( [ VariantProperty("aot_plugin", "name1", "val1a"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MockedAoTPlugin" vinfo = VariantInfo( @@ -745,7 +730,8 @@ def test_make_variant_dist_info_invalid_aot_plugin_property() -> None: vdesc = VariantDescription( [ VariantProperty("aot_plugin", "name1", "val1d"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MockedAoTPlugin" vinfo = VariantInfo( @@ -776,7 +762,8 @@ def test_make_variant_dist_info_invalid_aot_plugin_multi_value() -> None: vdesc = VariantDescription( [ VariantProperty("aot_plugin", "name1", "val1a"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MultiValueAoTPlugin" vinfo = VariantInfo( @@ -806,7 +793,8 @@ def test_make_variant_dist_info_really_invalid_build_plugin() -> None: vdesc = VariantDescription( [ VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="test", ) plugin_api = "tests.mocked_plugins:MockedPluginB" vinfo = VariantInfo( diff --git a/tests/test_utils.py b/tests/test_utils.py index c809073b..f6298aa5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -39,29 +39,29 @@ def test_get_combinations(configs: list[ProviderConfig]) -> None: ] assert list(get_combinations(configs, namespace_priorities)) == [ - VariantDescription([val1a, val2a, val3a]), - VariantDescription([val1a, val2a]), - VariantDescription([val1a, val2b, val3a]), - VariantDescription([val1a, val2b]), - VariantDescription([val1a, val2c, val3a]), - VariantDescription([val1a, val2c]), - VariantDescription([val1a, val3a]), - VariantDescription([val1a]), - VariantDescription([val1b, val2a, val3a]), - VariantDescription([val1b, val2a]), - VariantDescription([val1b, val2b, val3a]), - VariantDescription([val1b, val2b]), - VariantDescription([val1b, val2c, val3a]), - VariantDescription([val1b, val2c]), - VariantDescription([val1b, val3a]), - VariantDescription([val1b]), - VariantDescription([val2a, val3a]), - VariantDescription([val2a]), - VariantDescription([val2b, val3a]), - VariantDescription([val2b]), - VariantDescription([val2c, val3a]), - VariantDescription([val2c]), - VariantDescription([val3a]), + VariantDescription([val1a, val2a, val3a], label="0_0"), + VariantDescription([val1a, val2a], label="0_1"), + VariantDescription([val1a, val2b, val3a], label="0_2"), + VariantDescription([val1a, val2b], label="0_3"), + VariantDescription([val1a, val2c, val3a], label="0_4"), + VariantDescription([val1a, val2c], label="0_5"), + VariantDescription([val1a, val3a], label="0_6"), + VariantDescription([val1a], label="0_7"), + VariantDescription([val1b, val2a, val3a], label="0_8"), + VariantDescription([val1b, val2a], label="0_9"), + VariantDescription([val1b, val2b, val3a], label="0_10"), + VariantDescription([val1b, val2b], label="0_11"), + VariantDescription([val1b, val2c, val3a], label="0_12"), + VariantDescription([val1b, val2c], label="0_13"), + VariantDescription([val1b, val3a], label="0_14"), + VariantDescription([val1b], label="0_15"), + VariantDescription([val2a, val3a], label="1_0"), + VariantDescription([val2a], label="1_1"), + VariantDescription([val2b, val3a], label="1_2"), + VariantDescription([val2b], label="1_3"), + VariantDescription([val2c, val3a], label="1_4"), + VariantDescription([val2c], label="1_5"), + VariantDescription([val3a], label="2_0"), VariantDescription(), ] @@ -81,29 +81,29 @@ def test_get_combinations_flipped_order(configs: list[ProviderConfig]) -> None: ] assert list(get_combinations(configs, namespace_priorities)) == [ - VariantDescription([val1a, val2a, val3a]), - VariantDescription([val1a, val2b, val3a]), - VariantDescription([val1a, val2c, val3a]), - VariantDescription([val1a, val3a]), - VariantDescription([val1b, val2a, val3a]), - VariantDescription([val1b, val2b, val3a]), - VariantDescription([val1b, val2c, val3a]), - VariantDescription([val1b, val3a]), - VariantDescription([val2a, val3a]), - VariantDescription([val2b, val3a]), - VariantDescription([val2c, val3a]), - VariantDescription([val3a]), - VariantDescription([val1a, val2a]), - VariantDescription([val1a, val2b]), - VariantDescription([val1a, val2c]), - VariantDescription([val1a]), - VariantDescription([val1b, val2a]), - VariantDescription([val1b, val2b]), - VariantDescription([val1b, val2c]), - VariantDescription([val1b]), - VariantDescription([val2a]), - VariantDescription([val2b]), - VariantDescription([val2c]), + VariantDescription([val1a, val2a, val3a], label="0_0"), + VariantDescription([val1a, val2b, val3a], label="0_1"), + VariantDescription([val1a, val2c, val3a], label="0_2"), + VariantDescription([val1a, val3a], label="0_3"), + VariantDescription([val1b, val2a, val3a], label="0_4"), + VariantDescription([val1b, val2b, val3a], label="0_5"), + VariantDescription([val1b, val2c, val3a], label="0_6"), + VariantDescription([val1b, val3a], label="0_7"), + VariantDescription([val2a, val3a], label="0_8"), + VariantDescription([val2b, val3a], label="0_9"), + VariantDescription([val2c, val3a], label="0_10"), + VariantDescription([val3a], label="0_11"), + VariantDescription([val1a, val2a], label="1_0"), + VariantDescription([val1a, val2b], label="1_1"), + VariantDescription([val1a, val2c], label="1_2"), + VariantDescription([val1a], label="1_3"), + VariantDescription([val1b, val2a], label="1_4"), + VariantDescription([val1b, val2b], label="1_5"), + VariantDescription([val1b, val2c], label="1_6"), + VariantDescription([val1b], label="1_7"), + VariantDescription([val2a], label="2_0"), + VariantDescription([val2b], label="2_1"), + VariantDescription([val2c], label="2_2"), VariantDescription(), ] @@ -117,7 +117,8 @@ def test_get_combinations_one_one_namespace_one(configs: list[ProviderConfig]) - VariantDescription( [ VariantProperty("second_namespace", "name3", "val3a"), - ] + ], + label="0_0", ), VariantDescription(), ] @@ -136,17 +137,17 @@ def test_get_combinations_one_one_namespace_two(configs: list[ProviderConfig]) - ] assert list(get_combinations(configs, namespace_priorities)) == [ - VariantDescription([val1a, val2a]), - VariantDescription([val1a, val2b]), - VariantDescription([val1a, val2c]), - VariantDescription([val1a]), - VariantDescription([val1b, val2a]), - VariantDescription([val1b, val2b]), - VariantDescription([val1b, val2c]), - VariantDescription([val1b]), - VariantDescription([val2a]), - VariantDescription([val2b]), - VariantDescription([val2c]), + VariantDescription([val1a, val2a], label="0_0"), + VariantDescription([val1a, val2b], label="0_1"), + VariantDescription([val1a, val2c], label="0_2"), + VariantDescription([val1a], label="0_3"), + VariantDescription([val1b, val2a], label="0_4"), + VariantDescription([val1b, val2b], label="0_5"), + VariantDescription([val1b, val2c], label="0_6"), + VariantDescription([val1b], label="0_7"), + VariantDescription([val2a], label="1_0"), + VariantDescription([val2b], label="1_1"), + VariantDescription([val2c], label="1_2"), VariantDescription(), ] diff --git a/tests/utils.py b/tests/utils.py index ea05b9fb..4d894b4b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -51,8 +51,8 @@ def yield_all_values( yield [VariantProperty(namespace, feature, value)] for start in range(len(all_properties)): - for properties in yield_all_values(all_properties[start:]): - yield VariantDescription(properties) + for j, properties in enumerate(yield_all_values(all_properties[start:])): + yield VariantDescription(properties, label=f"{start}_{j}") # Finish by the null variant yield VariantDescription() From 71602f81b0497b49d07a24c5d5f8873cc8b5379c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 17:43:46 +0200 Subject: [PATCH 08/18] VariantDistInfo: use the label from `VariantDescription` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/test_variant_dist_info.py | 10 +++++----- variantlib/variant_dist_info.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_variant_dist_info.py b/tests/test_variant_dist_info.py index cd5c5845..1e78a4a8 100644 --- a/tests/test_variant_dist_info.py +++ b/tests/test_variant_dist_info.py @@ -85,17 +85,17 @@ def test_new_variant_dist_info() -> None: namespace_priorities=["ns"], providers={"ns": ProviderInfo(requires=["ns-pkg"])} ) variant_dist_info = VariantDistInfo(variant_info) - vdesc = VariantDescription([VariantProperty("ns", "f", "v")]) + vdesc = VariantDescription([VariantProperty("ns", "f", "v")], label="a") variant_dist_info.variant_desc = vdesc assert variant_dist_info.namespace_priorities == variant_info.namespace_priorities assert variant_dist_info.providers == variant_info.providers - assert variant_dist_info.variant_label == vdesc.hexdigest + assert variant_dist_info.variant_label == "a" assert variant_dist_info.variant_desc == vdesc # changing vdesc should update the label - vdesc2 = VariantDescription([VariantProperty("ns2", "f2", "v2")]) + vdesc2 = VariantDescription([VariantProperty("ns2", "f2", "v2")], label="b") variant_dist_info.variant_desc = vdesc2 - assert variant_dist_info.variant_label == vdesc2.hexdigest + assert variant_dist_info.variant_label == "b" assert variant_dist_info.variant_desc == vdesc2 # set a custom label @@ -105,5 +105,5 @@ def test_new_variant_dist_info() -> None: # changing vdesc should reset the label variant_dist_info.variant_desc = vdesc2 - assert variant_dist_info.variant_label == vdesc2.hexdigest + assert variant_dist_info.variant_label == "b" assert variant_dist_info.variant_desc == vdesc2 diff --git a/variantlib/variant_dist_info.py b/variantlib/variant_dist_info.py index 79f6cecc..bc3fd87f 100644 --- a/variantlib/variant_dist_info.py +++ b/variantlib/variant_dist_info.py @@ -56,4 +56,4 @@ def variant_desc(self) -> VariantDescription: @variant_desc.setter def variant_desc(self, new_desc: VariantDescription) -> None: - self.variants = {new_desc.hexdigest: new_desc} + self.variants = {new_desc.label: new_desc} From 17282d13c965a18b61ecb5ffdadbb9a663cd64c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 17:59:20 +0200 Subject: [PATCH 09/18] Fix remaining test failures and enable label validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/models/test_variant.py | 1 - tests/test_variants_json.py | 19 +++++++++++-------- variantlib/commands/get_variant_hash.py | 5 ++++- variantlib/commands/make_variant.py | 2 +- variantlib/models/variant.py | 17 +++++++---------- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/tests/models/test_variant.py b/tests/models/test_variant.py index 3485418d..62a107d1 100644 --- a/tests/models/test_variant.py +++ b/tests/models/test_variant.py @@ -427,7 +427,6 @@ def test_random_hexdigest(vdesc: VariantDescription) -> None: assert len(vdesc.hexdigest) == VARIANT_HASH_LENGTH -@pytest.mark.xfail(reason="Validation is disabled for porting") def test_null_variant_label(): with pytest.raises( ValidationError, diff --git a/tests/test_variants_json.py b/tests/test_variants_json.py index a8bc9822..51d2ec7e 100644 --- a/tests/test_variants_json.py +++ b/tests/test_variants_json.py @@ -317,16 +317,18 @@ def test_to_str() -> None: [ VariantProperty("ns1", "f1", "v1"), VariantProperty("ns2", "f2", "v1"), - ] + ], + label="a", ) vdesc2 = VariantDescription( [ VariantProperty("ns2", "f2", "v2"), - ] + ], + label="b", ) variants_json.variants = { - vdesc1.hexdigest: vdesc1, - vdesc2.hexdigest: vdesc2, + "a": vdesc1, + "b": vdesc2, } assert json.loads(variants_json.to_str()) == { VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, @@ -347,8 +349,8 @@ def test_to_str() -> None: }, }, VARIANTS_JSON_VARIANT_DATA_KEY: { - "b3b0305c": {"ns1": {"f1": ["v1"]}, "ns2": {"f2": ["v1"]}}, - "9177ff3f": {"ns2": {"f2": ["v2"]}}, + "a": {"ns1": {"f1": ["v1"]}, "ns2": {"f2": ["v1"]}}, + "b": {"ns2": {"f2": ["v2"]}}, }, } @@ -501,12 +503,13 @@ def test_merge_variants() -> None: def test_null_variant_label(): with pytest.raises( ValidationError, - match=rf"{NULL_VARIANT_LABEL!r} label can only be used for the null variant", + match=rf"{NULL_VARIANT_LABEL!r} label can be used only for the null variant", ): VariantsJson( {VARIANTS_JSON_VARIANT_DATA_KEY: {NULL_VARIANT_LABEL: {"x": {"y": ["z"]}}}} ) with pytest.raises( - ValidationError, match=rf"Null variant must use {NULL_VARIANT_LABEL!r} label" + ValidationError, + match=rf"Null variant must always use {NULL_VARIANT_LABEL!r} label", ): VariantsJson({VARIANTS_JSON_VARIANT_DATA_KEY: {"zuul": {}}}) diff --git a/variantlib/commands/get_variant_hash.py b/variantlib/commands/get_variant_hash.py index ee145cb2..c8fb3b34 100644 --- a/variantlib/commands/get_variant_hash.py +++ b/variantlib/commands/get_variant_hash.py @@ -6,6 +6,7 @@ from variantlib import __package_name__ from variantlib.api import VariantDescription from variantlib.api import VariantProperty +from variantlib.constants import NULL_VARIANT_LABEL def get_variant_hash(args: list[str]) -> None: @@ -35,6 +36,8 @@ def get_variant_hash(args: list[str]) -> None: def _print_variant_hash(properties: list[VariantProperty]) -> None: # Transform properties into a VariantDescription - vdesc = VariantDescription(properties=properties) + vdesc = VariantDescription( + properties=properties, label="na" if properties else NULL_VARIANT_LABEL + ) sys.stdout.write(f"{vdesc.hexdigest}\n") diff --git a/variantlib/commands/make_variant.py b/variantlib/commands/make_variant.py index cf424a03..793ed013 100644 --- a/variantlib/commands/make_variant.py +++ b/variantlib/commands/make_variant.py @@ -169,7 +169,7 @@ def _make_variant( if not is_null_variant: # Transform properties into a VariantDescription - vdesc = VariantDescription(properties=properties) + vdesc = VariantDescription(properties=properties, label="na") if validate_properties: env_factory: DefaultIsolatedEnv | nullcontext[None] diff --git a/variantlib/models/variant.py b/variantlib/models/variant.py index ea443c13..3787720f 100644 --- a/variantlib/models/variant.py +++ b/variantlib/models/variant.py @@ -187,18 +187,15 @@ def __post_init__(self) -> None: # Execute the validator super().__post_init__() - # TODO: enable once we're done porting - if False: - if self.is_null_variant(): - if self.label != NULL_VARIANT_LABEL: - raise ValidationError( - f"Null variant must always use {NULL_VARIANT_LABEL!r} label" - ) - elif self.label == NULL_VARIANT_LABEL: + if self.is_null_variant(): + if self.label != NULL_VARIANT_LABEL: raise ValidationError( - f"{NULL_VARIANT_LABEL!r} label can be used only for the null " - "variant" + f"Null variant must always use {NULL_VARIANT_LABEL!r} label" ) + elif self.label == NULL_VARIANT_LABEL: + raise ValidationError( + f"{NULL_VARIANT_LABEL!r} label can be used only for the null variant" + ) def is_null_variant(self) -> bool: """ From 29bfe17ffdb5f221d9dabaee754f502fdfc09ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 18:09:59 +0200 Subject: [PATCH 10/18] Default the non-null variant label to hexdigest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/models/variant.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/variantlib/models/variant.py b/variantlib/models/variant.py index 3787720f..18300482 100644 --- a/variantlib/models/variant.py +++ b/variantlib/models/variant.py @@ -162,7 +162,7 @@ class VariantDescription(BaseModel): default_factory=list, ) - label: str = field( + label: str | None = field( metadata={ "validator": lambda val: validate_and( [ @@ -172,7 +172,7 @@ class VariantDescription(BaseModel): value=val, ), }, - default=NULL_VARIANT_LABEL, + default=None, ) def __post_init__(self) -> None: @@ -184,10 +184,15 @@ def __post_init__(self) -> None: # Only "legal way" to modify a frozen dataclass attribute post init. object.__setattr__(self, "properties", sorted(self.properties)) - # Execute the validator - super().__post_init__() - - if self.is_null_variant(): + # default the label to "null" or hexdigest + if self.label is None: + with contextlib.suppress(AttributeError): + object.__setattr__( + self, + "label", + NULL_VARIANT_LABEL if self.is_null_variant() else self.hexdigest, + ) + elif self.is_null_variant(): if self.label != NULL_VARIANT_LABEL: raise ValidationError( f"Null variant must always use {NULL_VARIANT_LABEL!r} label" @@ -197,6 +202,9 @@ def __post_init__(self) -> None: f"{NULL_VARIANT_LABEL!r} label can be used only for the null variant" ) + # Execute the validator + super().__post_init__() + def is_null_variant(self) -> bool: """ Check if the variant is a null variant. From 11b44b3e7ff65b73388c7efe16455da8d88ade2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 18:28:15 +0200 Subject: [PATCH 11/18] VariantDistInfo: sync label in VariantDescription MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/test_variant_dist_info.py | 4 +++- variantlib/variant_dist_info.py | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_variant_dist_info.py b/tests/test_variant_dist_info.py index 1e78a4a8..c6c2850d 100644 --- a/tests/test_variant_dist_info.py +++ b/tests/test_variant_dist_info.py @@ -101,7 +101,9 @@ def test_new_variant_dist_info() -> None: # set a custom label variant_dist_info.variant_label = "fancy2" assert variant_dist_info.variant_label == "fancy2" - assert variant_dist_info.variant_desc == vdesc2 + assert variant_dist_info.variant_desc == VariantDescription( + vdesc2.properties, label="fancy2" + ) # changing vdesc should reset the label variant_dist_info.variant_desc = vdesc2 diff --git a/variantlib/variant_dist_info.py b/variantlib/variant_dist_info.py index bc3fd87f..7f3b99be 100644 --- a/variantlib/variant_dist_info.py +++ b/variantlib/variant_dist_info.py @@ -2,16 +2,13 @@ import json from dataclasses import dataclass -from typing import TYPE_CHECKING from variantlib.constants import VARIANT_DIST_INFO_FILENAME from variantlib.errors import ValidationError +from variantlib.models.variant import VariantDescription from variantlib.models.variant_info import VariantInfo from variantlib.variants_json import VariantsJson -if TYPE_CHECKING: - from variantlib.models.variant import VariantDescription - @dataclass(init=False) class VariantDistInfo(VariantsJson): @@ -47,6 +44,9 @@ def variant_label(self) -> str: @variant_label.setter def variant_label(self, new_label: str) -> None: + self.variant_desc = VariantDescription( + self.variant_desc.properties, label=new_label + ) self.variants = {new_label: self.variant_desc} @property @@ -56,4 +56,5 @@ def variant_desc(self) -> VariantDescription: @variant_desc.setter def variant_desc(self, new_desc: VariantDescription) -> None: + assert new_desc.label is not None self.variants = {new_desc.label: new_desc} From 0abf7de808e4b36a4c6650829e0050b444f6363c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 18:32:13 +0200 Subject: [PATCH 12/18] Remove the no-longer-necessary command hack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/commands/get_variant_hash.py | 5 +---- variantlib/commands/make_variant.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/variantlib/commands/get_variant_hash.py b/variantlib/commands/get_variant_hash.py index c8fb3b34..ee145cb2 100644 --- a/variantlib/commands/get_variant_hash.py +++ b/variantlib/commands/get_variant_hash.py @@ -6,7 +6,6 @@ from variantlib import __package_name__ from variantlib.api import VariantDescription from variantlib.api import VariantProperty -from variantlib.constants import NULL_VARIANT_LABEL def get_variant_hash(args: list[str]) -> None: @@ -36,8 +35,6 @@ def get_variant_hash(args: list[str]) -> None: def _print_variant_hash(properties: list[VariantProperty]) -> None: # Transform properties into a VariantDescription - vdesc = VariantDescription( - properties=properties, label="na" if properties else NULL_VARIANT_LABEL - ) + vdesc = VariantDescription(properties=properties) sys.stdout.write(f"{vdesc.hexdigest}\n") diff --git a/variantlib/commands/make_variant.py b/variantlib/commands/make_variant.py index 793ed013..cf424a03 100644 --- a/variantlib/commands/make_variant.py +++ b/variantlib/commands/make_variant.py @@ -169,7 +169,7 @@ def _make_variant( if not is_null_variant: # Transform properties into a VariantDescription - vdesc = VariantDescription(properties=properties, label="na") + vdesc = VariantDescription(properties=properties) if validate_properties: env_factory: DefaultIsolatedEnv | nullcontext[None] From 9612488f1655d91c251f17714430fdf735199440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 18:40:28 +0200 Subject: [PATCH 13/18] api: Update `get_variant_label()` to use `vdesc.label`, deprecate it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/commands/test_analyze_wheel.py | 2 +- variantlib/api.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/commands/test_analyze_wheel.py b/tests/commands/test_analyze_wheel.py index 889e8452..61c0e510 100644 --- a/tests/commands/test_analyze_wheel.py +++ b/tests/commands/test_analyze_wheel.py @@ -83,7 +83,7 @@ def test_analyze_wheel_variant_custom_label( assert ( capsys.readouterr().out == """\ -############################## Variant: `60567bd9` ############################# +############################## Variant: `foo` ############################# installable_plugin :: feat1 :: val1c ################################################################################ """ diff --git a/variantlib/api.py b/variantlib/api.py index e79c497a..924e4abd 100644 --- a/variantlib/api.py +++ b/variantlib/api.py @@ -352,14 +352,13 @@ def get_variant_label( Get the label corresponding to `variant_desc`. If `custom_label` is provided, validate it and use it. If `custom_label` is invalid, raises a `ValidationError`. + + This function is deprecated: use VariantDescription.label instead. """ if custom_label is None: - return ( - NULL_VARIANT_LABEL - if variant_desc.is_null_variant() - else variant_desc.hexdigest - ) + assert variant_desc.label is not None + return variant_desc.label if variant_desc.is_null_variant(): if custom_label != NULL_VARIANT_LABEL: From b177035a1031a7de7f2cbfda799cb865ac0a2df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 18:46:52 +0200 Subject: [PATCH 14/18] api: Deprecate remaining variant_label arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/api.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/variantlib/api.py b/variantlib/api.py index 924e4abd..bc83b76b 100644 --- a/variantlib/api.py +++ b/variantlib/api.py @@ -5,6 +5,7 @@ import itertools import logging import pathlib +import warnings from typing import TYPE_CHECKING from variantlib.configuration import VariantConfiguration @@ -185,7 +186,7 @@ def make_variant_dist_info( instead. variant_label specifies the variant label to use. If not specified, - the default of variant hash is used. + the default of variant hash is used. Deprecated: set vdesc.label instead. If expand_aot_plugin_properties is True, then default-priorities for ahead-of-time plugins will be filled with the current list @@ -198,6 +199,12 @@ def make_variant_dist_info( variant_info = VariantInfo() variant_json = VariantDistInfo(variant_info) variant_json.variant_desc = vdesc + if variant_label is not None: + warnings.warn( + "Passing variant_label is deprecated, provide VariantDescription() " + "with label instead", + stacklevel=2, + ) variant_json.variant_label = get_variant_label(vdesc, variant_label) if expand_aot_plugin_properties: @@ -330,14 +337,21 @@ def get_variant_environment_dict( ) -> dict[str, set[str] | str]: """Get the dict for packaging Marker.evaluate()""" + assert variant_desc.label is not None ret: dict[str, set[str] | str] = { "variant_namespaces": {vprop.namespace for vprop in variant_desc.properties}, "variant_features": { vprop.feature_object.to_str() for vprop in variant_desc.properties }, "variant_properties": {vprop.to_str() for vprop in variant_desc.properties}, + "variant_label": variant_desc.label, } if variant_label is not None: + warnings.warn( + "Passing variant_label is deprecated, provide VariantDescription() " + "with label instead", + stacklevel=2, + ) ret["variant_label"] = variant_label return ret From 568e1db0a4581e751abff41caaff4a3e8e36a51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 18:48:14 +0200 Subject: [PATCH 15/18] Also emit DeprecationWarning from `get_variant_label()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/variantlib/api.py b/variantlib/api.py index bc83b76b..a0df89f4 100644 --- a/variantlib/api.py +++ b/variantlib/api.py @@ -374,6 +374,11 @@ def get_variant_label( assert variant_desc.label is not None return variant_desc.label + warnings.warn( + "get_variant_label() is deprecated, use VariantDescription.label instead", + stacklevel=2, + ) + if variant_desc.is_null_variant(): if custom_label != NULL_VARIANT_LABEL: raise ValidationError( From 75d9ab860309a4e686b0b2feeb6c86c50bf0ae8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 19:34:03 +0200 Subject: [PATCH 16/18] Use a hack-default of "" instead of None to make typing easier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/api.py | 2 -- variantlib/models/variant.py | 6 +++--- variantlib/variant_dist_info.py | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/variantlib/api.py b/variantlib/api.py index a0df89f4..d1d26f9b 100644 --- a/variantlib/api.py +++ b/variantlib/api.py @@ -337,7 +337,6 @@ def get_variant_environment_dict( ) -> dict[str, set[str] | str]: """Get the dict for packaging Marker.evaluate()""" - assert variant_desc.label is not None ret: dict[str, set[str] | str] = { "variant_namespaces": {vprop.namespace for vprop in variant_desc.properties}, "variant_features": { @@ -371,7 +370,6 @@ def get_variant_label( """ if custom_label is None: - assert variant_desc.label is not None return variant_desc.label warnings.warn( diff --git a/variantlib/models/variant.py b/variantlib/models/variant.py index 18300482..c7369eb2 100644 --- a/variantlib/models/variant.py +++ b/variantlib/models/variant.py @@ -162,7 +162,7 @@ class VariantDescription(BaseModel): default_factory=list, ) - label: str | None = field( + label: str = field( metadata={ "validator": lambda val: validate_and( [ @@ -172,7 +172,7 @@ class VariantDescription(BaseModel): value=val, ), }, - default=None, + default="", ) def __post_init__(self) -> None: @@ -185,7 +185,7 @@ def __post_init__(self) -> None: object.__setattr__(self, "properties", sorted(self.properties)) # default the label to "null" or hexdigest - if self.label is None: + if not self.label: with contextlib.suppress(AttributeError): object.__setattr__( self, diff --git a/variantlib/variant_dist_info.py b/variantlib/variant_dist_info.py index 7f3b99be..9c2d3160 100644 --- a/variantlib/variant_dist_info.py +++ b/variantlib/variant_dist_info.py @@ -56,5 +56,4 @@ def variant_desc(self) -> VariantDescription: @variant_desc.setter def variant_desc(self, new_desc: VariantDescription) -> None: - assert new_desc.label is not None self.variants = {new_desc.label: new_desc} From c4f948673cb15648934101d0a6b6eadefb5a1ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 13 Apr 2026 19:35:54 +0200 Subject: [PATCH 17/18] api: Simplify `get_variants_by_priority()` to use vdesc labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/api.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/variantlib/api.py b/variantlib/api.py index d1d26f9b..2fdf628d 100644 --- a/variantlib/api.py +++ b/variantlib/api.py @@ -58,6 +58,8 @@ def get_variants_by_priority( if not isinstance(variants_json, VariantsJson): variants_json = VariantsJson(variants_json) + assert all(vdesc.label == label for label, vdesc in variants_json.variants.items()) + venv_python_executable = ( venv_python_executable if venv_python_executable is None @@ -77,14 +79,9 @@ def get_variants_by_priority( ) config = VariantConfiguration.get_config() - label_map = { - vdesc.hexdigest: label for label, vdesc in variants_json.variants.items() - } - # handle the implicit null variant - label_map.setdefault(VariantDescription([]).hexdigest, NULL_VARIANT_LABEL) return [ - label_map[vdesc.hexdigest] + vdesc.label for vdesc in sort_and_filter_supported_variants( list(variants_json.variants.values()), supported_vprops, From bed4a86ace11ed21ee9bf103c508aef758f2c473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 14 Apr 2026 16:08:27 +0200 Subject: [PATCH 18/18] Check that labels are synced when dumping variants.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/variants_json.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variantlib/variants_json.py b/variantlib/variants_json.py index 325956a1..52aabec9 100644 --- a/variantlib/variants_json.py +++ b/variantlib/variants_json.py @@ -90,6 +90,8 @@ def providers_dict(self) -> dict[str, dict[str, str | list[str] | bool]]: def to_str(self) -> str: """Serialize variants.json as a JSON string""" + assert all(label == vdesc.label for label, vdesc in self.variants.items()) + data: dict[str, Any] = { VARIANTS_JSON_SCHEMA_KEY: VARIANTS_JSON_SCHEMA_URL, VARIANT_INFO_DEFAULT_PRIO_KEY: dict(self._priorities_to_json()),