infrahub-server 1.2.6__py3-none-any.whl → 1.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. infrahub/cli/db.py +2 -0
  2. infrahub/cli/patch.py +153 -0
  3. infrahub/computed_attribute/models.py +81 -1
  4. infrahub/computed_attribute/tasks.py +34 -53
  5. infrahub/core/node/__init__.py +4 -1
  6. infrahub/core/query/ipam.py +7 -5
  7. infrahub/patch/__init__.py +0 -0
  8. infrahub/patch/constants.py +13 -0
  9. infrahub/patch/edge_adder.py +64 -0
  10. infrahub/patch/edge_deleter.py +33 -0
  11. infrahub/patch/edge_updater.py +28 -0
  12. infrahub/patch/models.py +98 -0
  13. infrahub/patch/plan_reader.py +107 -0
  14. infrahub/patch/plan_writer.py +92 -0
  15. infrahub/patch/queries/__init__.py +0 -0
  16. infrahub/patch/queries/base.py +17 -0
  17. infrahub/patch/runner.py +254 -0
  18. infrahub/patch/vertex_adder.py +61 -0
  19. infrahub/patch/vertex_deleter.py +33 -0
  20. infrahub/patch/vertex_updater.py +28 -0
  21. infrahub_sdk/checks.py +1 -1
  22. infrahub_sdk/ctl/cli_commands.py +2 -2
  23. infrahub_sdk/ctl/menu.py +56 -13
  24. infrahub_sdk/ctl/object.py +55 -5
  25. infrahub_sdk/ctl/utils.py +22 -1
  26. infrahub_sdk/exceptions.py +19 -1
  27. infrahub_sdk/node.py +42 -26
  28. infrahub_sdk/protocols_generator/__init__.py +0 -0
  29. infrahub_sdk/protocols_generator/constants.py +28 -0
  30. infrahub_sdk/{code_generator.py → protocols_generator/generator.py} +47 -34
  31. infrahub_sdk/protocols_generator/template.j2 +114 -0
  32. infrahub_sdk/schema/__init__.py +110 -74
  33. infrahub_sdk/schema/main.py +36 -2
  34. infrahub_sdk/schema/repository.py +2 -0
  35. infrahub_sdk/spec/menu.py +3 -3
  36. infrahub_sdk/spec/object.py +522 -41
  37. infrahub_sdk/testing/docker.py +4 -5
  38. infrahub_sdk/testing/schemas/animal.py +7 -0
  39. infrahub_sdk/yaml.py +63 -7
  40. {infrahub_server-1.2.6.dist-info → infrahub_server-1.2.7.dist-info}/METADATA +1 -1
  41. {infrahub_server-1.2.6.dist-info → infrahub_server-1.2.7.dist-info}/RECORD +44 -27
  42. infrahub_sdk/ctl/constants.py +0 -115
  43. {infrahub_server-1.2.6.dist-info → infrahub_server-1.2.7.dist-info}/LICENSE.txt +0 -0
  44. {infrahub_server-1.2.6.dist-info → infrahub_server-1.2.7.dist-info}/WHEEL +0 -0
  45. {infrahub_server-1.2.6.dist-info → infrahub_server-1.2.7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,114 @@
1
+ #
2
+ # Generated by "infrahubctl protocols"
3
+ #
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING, Optional
8
+
9
+ from infrahub_sdk.protocols import {{ "CoreNode" | syncify(sync) }}, {{ base_protocols | join(', ') }}
10
+
11
+ if TYPE_CHECKING:
12
+ from infrahub_sdk.node import {{ "RelatedNode" | syncify(sync) }}, {{ "RelationshipManager" | syncify(sync) }}
13
+ from infrahub_sdk.protocols_base import (
14
+ AnyAttribute,
15
+ AnyAttributeOptional,
16
+ String,
17
+ StringOptional,
18
+ Integer,
19
+ IntegerOptional,
20
+ Boolean,
21
+ BooleanOptional,
22
+ DateTime,
23
+ DateTimeOptional,
24
+ Dropdown,
25
+ DropdownOptional,
26
+ HashedPassword,
27
+ HashedPasswordOptional,
28
+ MacAddress,
29
+ MacAddressOptional,
30
+ IPHost,
31
+ IPHostOptional,
32
+ IPNetwork,
33
+ IPNetworkOptional,
34
+ JSONAttribute,
35
+ JSONAttributeOptional,
36
+ ListAttribute,
37
+ ListAttributeOptional,
38
+ URL,
39
+ URLOptional,
40
+ )
41
+
42
+ {% for generic in generics %}
43
+
44
+ class {{ generic.namespace + generic.name }}({{core_node_name}}):
45
+ {% if not generic.attributes|default([]) and not generic.relationships|default([]) %}
46
+ pass
47
+ {% endif %}
48
+ {% for attribute in generic.attributes | sort(attribute='name') | default([]) %}
49
+ {{ attribute | render_attribute }}
50
+ {% endfor %}
51
+ {% for relationship in generic.relationships | sort(attribute='name') | default([]) %}
52
+ {{ relationship | render_relationship(sync) }}
53
+ {% endfor %}
54
+ {% if generic.hierarchical | default(false) %}
55
+ parent: {{ "RelatedNode" | syncify(sync) }}
56
+ children: {{ "RelationshipManager" | syncify(sync) }}
57
+ {% endif %}
58
+ {% endfor %}
59
+
60
+
61
+ {% for node in nodes %}
62
+
63
+ class {{ node.namespace + node.name }}({{ node.inherit_from | syncify(sync) | join(", ") or core_node_name }}):
64
+ {% if not node.attributes|default([]) and not node.relationships|default([]) %}
65
+ pass
66
+ {% endif %}
67
+ {% for attribute in node.attributes | sort(attribute='name') | default([]) %}
68
+ {{ attribute | render_attribute }}
69
+ {% endfor %}
70
+ {% for relationship in node.relationships | sort(attribute='name') | default([]) %}
71
+ {{ relationship | render_relationship(sync) }}
72
+ {% endfor %}
73
+ {% if node.hierarchical | default(false) %}
74
+ parent: {{ "RelatedNode" | syncify(sync) }}
75
+ children: {{ "RelationshipManager" | syncify(sync) }}
76
+ {% endif %}
77
+
78
+ {% endfor %}
79
+
80
+
81
+ {% for node in profiles %}
82
+
83
+ class {{ node.namespace + node.name }}({{ node.inherit_from | syncify(sync) | join(", ") or core_node_name }}):
84
+ {% if not node.attributes|default([]) and not node.relationships|default([]) %}
85
+ pass
86
+ {% endif %}
87
+ {% for attribute in node.attributes | sort(attribute='name') | default([]) %}
88
+ {{ attribute | render_attribute }}
89
+ {% endfor %}
90
+ {% for relationship in node.relationships | sort(attribute='name') | default([]) %}
91
+ {{ relationship | render_relationship(sync) }}
92
+ {% endfor %}
93
+ {% if node.hierarchical | default(false) %}
94
+ parent: {{ "RelatedNode" | syncify(sync) }}
95
+ children: {{ "RelationshipManager" | syncify(sync) }}
96
+ {% endif %}
97
+
98
+ {% endfor %}
99
+
100
+
101
+ {% for node in templates %}
102
+
103
+ class {{ node.namespace + node.name }}({{ node.inherit_from | syncify(sync) | join(", ") or core_node_name }}):
104
+ {% if not node.attributes|default([]) and not node.relationships|default([]) %}
105
+ pass
106
+ {% endif %}
107
+ {% for attribute in node.attributes | sort(attribute='name') | default([]) %}
108
+ {{ attribute | render_attribute }}
109
+ {% endfor %}
110
+ {% for relationship in node.relationships | sort(attribute='name') | default([]) %}
111
+ {{ relationship | render_relationship(sync) }}
112
+ {% endfor %}
113
+
114
+ {% endfor %}
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import json
5
+ import warnings
5
6
  from collections.abc import MutableMapping
6
7
  from enum import Enum
7
8
  from time import sleep
@@ -13,6 +14,7 @@ from pydantic import BaseModel, Field
13
14
  from typing_extensions import TypeAlias
14
15
 
15
16
  from ..exceptions import (
17
+ BranchNotFoundError,
16
18
  InvalidResponseError,
17
19
  JsonDecodeError,
18
20
  SchemaNotFoundError,
@@ -89,6 +91,13 @@ MainSchemaTypesAll: TypeAlias = Union[
89
91
 
90
92
 
91
93
  class InfrahubSchemaBase:
94
+ client: InfrahubClient | InfrahubClientSync
95
+ cache: dict[str, BranchSchema]
96
+
97
+ def __init__(self, client: InfrahubClient | InfrahubClientSync):
98
+ self.client = client
99
+ self.cache = {}
100
+
92
101
  def validate(self, data: dict[str, Any]) -> None:
93
102
  SchemaRoot(**data)
94
103
 
@@ -101,6 +110,23 @@ class InfrahubSchemaBase:
101
110
  message=f"{key} is not a valid value for {identifier}",
102
111
  )
103
112
 
113
+ def set_cache(self, schema: dict[str, Any] | SchemaRootAPI | BranchSchema, branch: str | None = None) -> None:
114
+ """
115
+ Set the cache manually (primarily for unit testing)
116
+
117
+ Args:
118
+ schema: The schema to set the cache as provided by the /api/schema endpoint either in dict or SchemaRootAPI format
119
+ branch: The name of the branch to set the cache for.
120
+ """
121
+ branch = branch or self.client.default_branch
122
+
123
+ if isinstance(schema, SchemaRootAPI):
124
+ schema = BranchSchema.from_schema_root_api(data=schema)
125
+ elif isinstance(schema, dict):
126
+ schema = BranchSchema.from_api_response(data=schema)
127
+
128
+ self.cache[branch] = schema
129
+
104
130
  def generate_payload_create(
105
131
  self,
106
132
  schema: MainSchemaTypesAPI,
@@ -167,11 +193,37 @@ class InfrahubSchemaBase:
167
193
 
168
194
  raise ValueError("schema must be a protocol or a string")
169
195
 
196
+ @staticmethod
197
+ def _parse_schema_response(response: httpx.Response, branch: str) -> MutableMapping[str, Any]:
198
+ if response.status_code == 400:
199
+ raise BranchNotFoundError(
200
+ identifier=branch, message=f"The requested branch was not found on the server [{branch}]"
201
+ )
202
+ response.raise_for_status()
203
+
204
+ try:
205
+ data: MutableMapping[str, Any] = response.json()
206
+ except json.decoder.JSONDecodeError as exc:
207
+ raise JsonDecodeError(
208
+ message=f"Invalid Schema response received from the server at {response.url}: {response.text} [{response.status_code}] ",
209
+ content=response.text,
210
+ url=str(response.url),
211
+ ) from exc
212
+
213
+ return data
214
+
215
+ @staticmethod
216
+ def _deprecated_schema_timeout() -> None:
217
+ warnings.warn(
218
+ "The 'timeout' parameter is deprecated while fetching the schema and will be removed version 2.0.0 of the Infrahub Python SDK. "
219
+ "Use client.default_timeout instead.",
220
+ DeprecationWarning,
221
+ stacklevel=2,
222
+ )
223
+
170
224
 
171
225
  class InfrahubSchema(InfrahubSchemaBase):
172
- def __init__(self, client: InfrahubClient):
173
- self.client = client
174
- self.cache: dict[str, BranchSchema] = {}
226
+ client: InfrahubClient
175
227
 
176
228
  async def get(
177
229
  self,
@@ -184,8 +236,11 @@ class InfrahubSchema(InfrahubSchemaBase):
184
236
 
185
237
  kind_str = self._get_schema_name(schema=kind)
186
238
 
239
+ if timeout:
240
+ self._deprecated_schema_timeout()
241
+
187
242
  if refresh:
188
- self.cache[branch] = await self._fetch(branch=branch, timeout=timeout)
243
+ self.cache[branch] = await self._fetch(branch=branch)
189
244
 
190
245
  if branch in self.cache and kind_str in self.cache[branch].nodes:
191
246
  return self.cache[branch].nodes[kind_str]
@@ -193,7 +248,7 @@ class InfrahubSchema(InfrahubSchemaBase):
193
248
  # Fetching the latest schema from the server if we didn't fetch it earlier
194
249
  # because we coulnd't find the object on the local cache
195
250
  if not refresh:
196
- self.cache[branch] = await self._fetch(branch=branch, timeout=timeout)
251
+ self.cache[branch] = await self._fetch(branch=branch)
197
252
 
198
253
  if branch in self.cache and kind_str in self.cache[branch].nodes:
199
254
  return self.cache[branch].nodes[kind_str]
@@ -396,67 +451,45 @@ class InfrahubSchema(InfrahubSchemaBase):
396
451
  )
397
452
 
398
453
  async def fetch(
399
- self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None
454
+ self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None, populate_cache: bool = True
400
455
  ) -> MutableMapping[str, MainSchemaTypesAPI]:
401
456
  """Fetch the schema from the server for a given branch.
402
457
 
403
458
  Args:
404
- branch (str): Name of the branch to fetch the schema for.
405
- timeout (int, optional): Overrides default timeout used when querying the GraphQL API. Specified in seconds.
459
+ branch: Name of the branch to fetch the schema for.
460
+ timeout: Overrides default timeout used when querying the schema. deprecated.
461
+ populate_cache: Whether to populate the cache with the fetched schema. Defaults to True.
406
462
 
407
463
  Returns:
408
464
  dict[str, MainSchemaTypes]: Dictionary of all schema organized by kind
409
465
  """
410
- branch_schema = await self._fetch(branch=branch, namespaces=namespaces, timeout=timeout)
466
+
467
+ if timeout:
468
+ self._deprecated_schema_timeout()
469
+
470
+ branch_schema = await self._fetch(branch=branch, namespaces=namespaces)
471
+
472
+ if populate_cache:
473
+ self.cache[branch] = branch_schema
474
+
411
475
  return branch_schema.nodes
412
476
 
413
- async def _fetch(
414
- self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None
415
- ) -> BranchSchema:
477
+ async def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema:
416
478
  url_parts = [("branch", branch)]
417
479
  if namespaces:
418
480
  url_parts.extend([("namespaces", ns) for ns in namespaces])
419
481
  query_params = urlencode(url_parts)
420
482
  url = f"{self.client.address}/api/schema?{query_params}"
421
483
 
422
- response = await self.client._get(url=url, timeout=timeout)
423
- response.raise_for_status()
424
-
425
- try:
426
- data: MutableMapping[str, Any] = response.json()
427
- except json.decoder.JSONDecodeError as exc:
428
- raise JsonDecodeError(
429
- message=f"Invalid Schema response received from the server at {response.url}: {response.text} [{response.status_code}] ",
430
- content=response.text,
431
- url=response.url,
432
- ) from exc
433
-
434
- nodes: MutableMapping[str, MainSchemaTypesAPI] = {}
435
- for node_schema in data.get("nodes", []):
436
- node = NodeSchemaAPI(**node_schema)
437
- nodes[node.kind] = node
438
-
439
- for generic_schema in data.get("generics", []):
440
- generic = GenericSchemaAPI(**generic_schema)
441
- nodes[generic.kind] = generic
442
-
443
- for profile_schema in data.get("profiles", []):
444
- profile = ProfileSchemaAPI(**profile_schema)
445
- nodes[profile.kind] = profile
484
+ response = await self.client._get(url=url)
446
485
 
447
- for template_schema in data.get("templates", []):
448
- template = TemplateSchemaAPI(**template_schema)
449
- nodes[template.kind] = template
486
+ data = self._parse_schema_response(response=response, branch=branch)
450
487
 
451
- schema_hash = data.get("main", "")
452
-
453
- return BranchSchema(hash=schema_hash, nodes=nodes)
488
+ return BranchSchema.from_api_response(data=data)
454
489
 
455
490
 
456
491
  class InfrahubSchemaSync(InfrahubSchemaBase):
457
- def __init__(self, client: InfrahubClientSync):
458
- self.client = client
459
- self.cache: dict[str, BranchSchema] = {}
492
+ client: InfrahubClientSync
460
493
 
461
494
  def all(
462
495
  self,
@@ -494,10 +527,25 @@ class InfrahubSchemaSync(InfrahubSchemaBase):
494
527
  refresh: bool = False,
495
528
  timeout: int | None = None,
496
529
  ) -> MainSchemaTypesAPI:
530
+ """
531
+ Retrieve a specific schema object from the server.
532
+
533
+ Args:
534
+ kind: The kind of schema object to retrieve.
535
+ branch: The branch to retrieve the schema from.
536
+ refresh: Whether to refresh the schema.
537
+ timeout: Overrides default timeout used when querying the GraphQL API. Specified in seconds (deprecated).
538
+
539
+ Returns:
540
+ MainSchemaTypes: The schema object.
541
+ """
497
542
  branch = branch or self.client.default_branch
498
543
 
499
544
  kind_str = self._get_schema_name(schema=kind)
500
545
 
546
+ if timeout:
547
+ self._deprecated_schema_timeout()
548
+
501
549
  if refresh:
502
550
  self.cache[branch] = self._fetch(branch=branch)
503
551
 
@@ -507,7 +555,7 @@ class InfrahubSchemaSync(InfrahubSchemaBase):
507
555
  # Fetching the latest schema from the server if we didn't fetch it earlier
508
556
  # because we coulnd't find the object on the local cache
509
557
  if not refresh:
510
- self.cache[branch] = self._fetch(branch=branch, timeout=timeout)
558
+ self.cache[branch] = self._fetch(branch=branch)
511
559
 
512
560
  if branch in self.cache and kind_str in self.cache[branch].nodes:
513
561
  return self.cache[branch].nodes[kind_str]
@@ -627,51 +675,39 @@ class InfrahubSchemaSync(InfrahubSchemaBase):
627
675
  )
628
676
 
629
677
  def fetch(
630
- self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None
678
+ self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None, populate_cache: bool = True
631
679
  ) -> MutableMapping[str, MainSchemaTypesAPI]:
632
680
  """Fetch the schema from the server for a given branch.
633
681
 
634
682
  Args:
635
- branch (str): Name of the branch to fetch the schema for.
636
- timeout (int, optional): Overrides default timeout used when querying the GraphQL API. Specified in seconds.
683
+ branch: Name of the branch to fetch the schema for.
684
+ timeout: Overrides default timeout used when querying the GraphQL API. Specified in seconds (deprecated).
685
+ populate_cache: Whether to populate the cache with the fetched schema. Defaults to True.
637
686
 
638
687
  Returns:
639
688
  dict[str, MainSchemaTypes]: Dictionary of all schema organized by kind
640
689
  """
641
- branch_schema = self._fetch(branch=branch, namespaces=namespaces, timeout=timeout)
690
+ if timeout:
691
+ self._deprecated_schema_timeout()
692
+
693
+ branch_schema = self._fetch(branch=branch, namespaces=namespaces)
694
+
695
+ if populate_cache:
696
+ self.cache[branch] = branch_schema
697
+
642
698
  return branch_schema.nodes
643
699
 
644
- def _fetch(self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None) -> BranchSchema:
700
+ def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema:
645
701
  url_parts = [("branch", branch)]
646
702
  if namespaces:
647
703
  url_parts.extend([("namespaces", ns) for ns in namespaces])
648
704
  query_params = urlencode(url_parts)
649
705
  url = f"{self.client.address}/api/schema?{query_params}"
650
- response = self.client._get(url=url, timeout=timeout)
651
- response.raise_for_status()
652
-
653
- data: MutableMapping[str, Any] = response.json()
654
-
655
- nodes: MutableMapping[str, MainSchemaTypesAPI] = {}
656
- for node_schema in data.get("nodes", []):
657
- node = NodeSchemaAPI(**node_schema)
658
- nodes[node.kind] = node
659
-
660
- for generic_schema in data.get("generics", []):
661
- generic = GenericSchemaAPI(**generic_schema)
662
- nodes[generic.kind] = generic
663
-
664
- for profile_schema in data.get("profiles", []):
665
- profile = ProfileSchemaAPI(**profile_schema)
666
- nodes[profile.kind] = profile
667
-
668
- for template_schema in data.get("templates", []):
669
- template = TemplateSchemaAPI(**template_schema)
670
- nodes[template.kind] = template
706
+ response = self.client._get(url=url)
671
707
 
672
- schema_hash = data.get("main", "")
708
+ data = self._parse_schema_response(response=response, branch=branch)
673
709
 
674
- return BranchSchema(hash=schema_hash, nodes=nodes)
710
+ return BranchSchema.from_api_response(data=data)
675
711
 
676
712
  def load(
677
713
  self, schemas: list[dict], branch: str | None = None, wait_until_converged: bool = False
@@ -6,6 +6,7 @@ from enum import Enum
6
6
  from typing import TYPE_CHECKING, Any, Union
7
7
 
8
8
  from pydantic import BaseModel, ConfigDict, Field
9
+ from typing_extensions import Self
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from ..node import InfrahubNode, InfrahubNodeSync
@@ -231,7 +232,11 @@ class BaseSchemaAttrRelAPI(BaseModel):
231
232
 
232
233
  @property
233
234
  def mandatory_attribute_names(self) -> list[str]:
234
- return [item.name for item in self.attributes if not item.optional and item.default_value is None]
235
+ return [
236
+ item.name
237
+ for item in self.attributes
238
+ if (not item.optional and item.default_value is None) and not item.read_only
239
+ ]
235
240
 
236
241
  @property
237
242
  def mandatory_relationship_names(self) -> list[str]:
@@ -344,7 +349,7 @@ class SchemaRoot(BaseModel):
344
349
  class SchemaRootAPI(BaseModel):
345
350
  model_config = ConfigDict(use_enum_values=True)
346
351
 
347
- version: str
352
+ main: str | None = None
348
353
  generics: list[GenericSchemaAPI] = Field(default_factory=list)
349
354
  nodes: list[NodeSchemaAPI] = Field(default_factory=list)
350
355
  profiles: list[ProfileSchemaAPI] = Field(default_factory=list)
@@ -356,3 +361,32 @@ class BranchSchema(BaseModel):
356
361
  nodes: MutableMapping[str, GenericSchemaAPI | NodeSchemaAPI | ProfileSchemaAPI | TemplateSchemaAPI] = Field(
357
362
  default_factory=dict
358
363
  )
364
+
365
+ @classmethod
366
+ def from_api_response(cls, data: MutableMapping[str, Any]) -> Self:
367
+ """
368
+ Convert an API response from /api/schema into a BranchSchema object.
369
+ """
370
+ return cls.from_schema_root_api(data=SchemaRootAPI(**data))
371
+
372
+ @classmethod
373
+ def from_schema_root_api(cls, data: SchemaRootAPI) -> Self:
374
+ """
375
+ Convert a SchemaRootAPI object to a BranchSchema object.
376
+ """
377
+ nodes: MutableMapping[str, GenericSchemaAPI | NodeSchemaAPI | ProfileSchemaAPI | TemplateSchemaAPI] = {}
378
+ for node in data.nodes:
379
+ nodes[node.kind] = node
380
+
381
+ for generic in data.generics:
382
+ nodes[generic.kind] = generic
383
+
384
+ for profile in data.profiles:
385
+ nodes[profile.kind] = profile
386
+
387
+ for template in data.templates:
388
+ nodes[template.kind] = template
389
+
390
+ schema_hash = data.main or ""
391
+
392
+ return cls(hash=schema_hash, nodes=nodes)
@@ -83,6 +83,7 @@ class InfrahubCheckDefinitionConfig(InfrahubRepositoryConfigElement):
83
83
 
84
84
  class InfrahubGeneratorDefinitionConfig(InfrahubRepositoryConfigElement):
85
85
  model_config = ConfigDict(extra="forbid")
86
+
86
87
  name: str = Field(..., description="The name of the Generator Definition")
87
88
  file_path: Path = Field(..., description="The file within the repository with the generator code.")
88
89
  query: str = Field(..., description="The GraphQL query to use as input.")
@@ -112,6 +113,7 @@ class InfrahubGeneratorDefinitionConfig(InfrahubRepositoryConfigElement):
112
113
 
113
114
  class InfrahubPythonTransformConfig(InfrahubRepositoryConfigElement):
114
115
  model_config = ConfigDict(extra="forbid")
116
+
115
117
  name: str = Field(..., description="The name of the Transform")
116
118
  file_path: Path = Field(..., description="The file within the repository with the transform code.")
117
119
  class_name: str = Field(default="Transform", description="The name of the transform class to run.")
infrahub_sdk/spec/menu.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from ..yaml import InfrahubFile, InfrahubFileKind
4
- from .object import InfrahubObjectFileData
4
+ from .object import InfrahubObjectFileData, ObjectFile
5
5
 
6
6
 
7
7
  class InfrahubMenuFileData(InfrahubObjectFileData):
@@ -18,7 +18,7 @@ class InfrahubMenuFileData(InfrahubObjectFileData):
18
18
  return data
19
19
 
20
20
 
21
- class MenuFile(InfrahubFile):
21
+ class MenuFile(ObjectFile):
22
22
  _spec: InfrahubMenuFileData | None = None
23
23
 
24
24
  @property
@@ -28,7 +28,7 @@ class MenuFile(InfrahubFile):
28
28
  return self._spec
29
29
 
30
30
  def validate_content(self) -> None:
31
- super().validate_content()
31
+ InfrahubFile.validate_content(self)
32
32
  if self.kind != InfrahubFileKind.MENU:
33
33
  raise ValueError("File is not an Infrahub Menu file")
34
34
  self._spec = InfrahubMenuFileData(**self.data.spec)