Merge ~artivis/lpci:feature/yaml-alias into lpci:main

Proposed by jeremie
Status: Needs review
Proposed branch: ~artivis/lpci:feature/yaml-alias
Merge into: lpci:main
Diff against target: 105 lines (+85/-1)
2 files modified
lpcraft/config.py (+1/-1)
lpcraft/tests/test_config.py (+84/-0)
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+435370@code.launchpad.net

Description of the change

Changes pydantic configuration for extra fields from 'forbid' to 'ignore'. This allows defining extra fields which can be used to e.g. define yaml anchors. Aliases are resolved to anchors by pyyaml before being ignored (filtered out) by pydantic.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

I'm concerned about this approach, because it would mean that people's configuration files might break when we add new official fields.

Is there some way that we could at least confine field names used for anchors to some kind of namespace?

Revision history for this message
jeremie (artivis) wrote :

> Is there some way that we could at least confine field names used for anchors
> to some kind of namespace?

Thanks for the prompt review.
Another option would be to implement a pydantic root_validator that simply filters out entries at the root level based on a prefix - see here (https://git.launchpad.net/~artivis/lpcraft/tree/lpcraft/config.py?h=feature/yaml-alias-filter#n269). Note that the prefix I used is a simple dot but it could be more elaborated. For instance Docker Compose as a similar mechanism called extension fields using the prefix 'x-' (https://docs.docker.com/compose/compose-file/compose-file-v3/#extension-fields).

Revision history for this message
Jürgen Gmach (jugmac00) wrote :
Revision history for this message
Jürgen Gmach (jugmac00) wrote :

On our internal channels I have proposed a solution which leverages an external tool (or Python one-liner) to convert an enhanced version of a YAML file including alias/includes into a standard YAML file which we already support as of now:
https://chat.canonical.com/canonical/pl/r71cgk3ejtr1zmbfnfitbi187c

Let's keep the conversation on the chat until we come to a conclusion.

Unmerged commits

9e4b6f6... by jeremie

couple tests yaml anchor/alias

Signed-off-by: artivis <email address hidden>

Succeeded
[SUCCEEDED] test:0 (build)
[SUCCEEDED] build:0 (build)
12 of 2 results
e31b622... by jeremie

pydantic ignore extra fields in config

Signed-off-by: artivis <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lpcraft/config.py b/lpcraft/config.py
index d228270..eaaf73c 100644
--- a/lpcraft/config.py
+++ b/lpcraft/config.py
@@ -31,7 +31,7 @@ class _Identifier(pydantic.ConstrainedStr):
3131
32class ModelConfigDefaults(32class ModelConfigDefaults(
33 pydantic.BaseModel,33 pydantic.BaseModel,
34 extra=pydantic.Extra.forbid,34 extra=pydantic.Extra.ignore,
35 alias_generator=lambda s: s.replace("_", "-"),35 alias_generator=lambda s: s.replace("_", "-"),
36 underscore_attrs_are_private=True,36 underscore_attrs_are_private=True,
37):37):
diff --git a/lpcraft/tests/test_config.py b/lpcraft/tests/test_config.py
index 6c69eab..8695520 100644
--- a/lpcraft/tests/test_config.py
+++ b/lpcraft/tests/test_config.py
@@ -825,3 +825,87 @@ class TestConfig(TestCase):
825 Config.load,825 Config.load,
826 path,826 path,
827 )827 )
828
829 def test_yaml_anchor_alias_single_value(self):
830 # A yaml alias is resolved to an anchor holding a single value.
831 path = self.create_config(
832 dedent(
833 """
834 pipeline:
835 - test
836
837 jobs:
838 test-a:
839 series: &myseries focal
840 architectures: [amd64]
841
842 test-b:
843 series: *myseries
844 architectures: [amd64]
845 """
846 )
847 )
848 config = Config.load(path)
849 self.assertEqual(
850 config.jobs["test-a"][0].series, config.jobs["test-b"][0].series
851 )
852 self.assertEqual("focal", config.jobs["test-b"][0].series)
853
854 def test_yaml_anchor_alias_single_value_at_root(self):
855 # A yaml alias is resolved to an anchor holding a single value.
856 path = self.create_config(
857 dedent(
858 """
859 .series: &myseries focal
860
861 pipeline:
862 - test
863
864 jobs:
865 test:
866 series: *myseries
867 architectures: [amd64]
868 """
869 )
870 )
871 config = Config.load(path)
872 self.assertEqual("focal", config.jobs["test"][0].series)
873
874 def test_yaml_anchor_alias_job(self):
875 # A yaml alias is resolved to an anchor holding a whole job.
876 # The root-level extra filed is filtered out by pydantic.
877 path = self.create_config(
878 dedent(
879 """
880 .test: &mytest
881 series: focal
882 architectures: [amd64]
883
884 pipeline:
885 - test
886
887 jobs:
888 test: *mytest
889 """
890 )
891 )
892 config = Config.load(path)
893
894 self.assertThat(
895 config,
896 MatchesStructure(
897 pipeline=Equals([["test"]]),
898 jobs=MatchesDict(
899 {
900 "test": MatchesListwise(
901 [
902 MatchesStructure.byEquality(
903 series="focal",
904 architectures=["amd64"],
905 )
906 ]
907 )
908 }
909 ),
910 ),
911 )

Subscribers

People subscribed via source and target branches