infrahub-server 1.2.2__py3-none-any.whl → 1.2.3__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 (36) hide show
  1. infrahub/computed_attribute/tasks.py +8 -8
  2. infrahub/config.py +3 -0
  3. infrahub/core/graph/__init__.py +1 -1
  4. infrahub/core/migrations/graph/__init__.py +4 -1
  5. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +69 -0
  6. infrahub/core/models.py +6 -0
  7. infrahub/core/node/__init__.py +4 -4
  8. infrahub/core/node/constraints/grouped_uniqueness.py +24 -9
  9. infrahub/core/query/ipam.py +1 -1
  10. infrahub/core/schema/schema_branch.py +14 -5
  11. infrahub/git/integrator.py +9 -7
  12. infrahub/menu/repository.py +6 -6
  13. infrahub_sdk/client.py +6 -6
  14. infrahub_sdk/ctl/cli_commands.py +32 -37
  15. infrahub_sdk/ctl/render.py +39 -0
  16. infrahub_sdk/exceptions.py +6 -2
  17. infrahub_sdk/generator.py +1 -1
  18. infrahub_sdk/node.py +38 -11
  19. infrahub_sdk/protocols_base.py +8 -1
  20. infrahub_sdk/pytest_plugin/items/jinja2_transform.py +22 -26
  21. infrahub_sdk/store.py +351 -75
  22. infrahub_sdk/template/__init__.py +209 -0
  23. infrahub_sdk/template/exceptions.py +38 -0
  24. infrahub_sdk/template/filters.py +151 -0
  25. infrahub_sdk/template/models.py +10 -0
  26. infrahub_sdk/utils.py +7 -0
  27. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.3.dist-info}/METADATA +2 -1
  28. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.3.dist-info}/RECORD +34 -31
  29. infrahub_testcontainers/container.py +2 -0
  30. infrahub_testcontainers/docker-compose.test.yml +1 -0
  31. infrahub_testcontainers/haproxy.cfg +3 -3
  32. infrahub/support/__init__.py +0 -0
  33. infrahub/support/macro.py +0 -69
  34. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.3.dist-info}/LICENSE.txt +0 -0
  35. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.3.dist-info}/WHEEL +0 -0
  36. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.3.dist-info}/entry_points.txt +0 -0
infrahub_sdk/store.py CHANGED
@@ -1,16 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections import defaultdict
4
- from typing import TYPE_CHECKING, Any, Literal, overload
3
+ import warnings
4
+ from typing import TYPE_CHECKING, Literal, overload
5
5
 
6
- from .exceptions import NodeNotFoundError
6
+ from .exceptions import NodeInvalidError, NodeNotFoundError
7
+ from .node import parse_human_friendly_id
7
8
 
8
9
  if TYPE_CHECKING:
9
- from .client import SchemaType
10
+ from .client import SchemaType, SchemaTypeSync
10
11
  from .node import InfrahubNode, InfrahubNodeSync
12
+ from .protocols_base import CoreNode, CoreNodeSync
11
13
 
12
14
 
13
- def get_schema_name(schema: str | type[SchemaType] | None = None) -> str | None:
15
+ def get_schema_name(schema: type[SchemaType | SchemaTypeSync] | str | None = None) -> str | None:
14
16
  if isinstance(schema, str):
15
17
  return schema
16
18
 
@@ -20,130 +22,404 @@ def get_schema_name(schema: str | type[SchemaType] | None = None) -> str | None:
20
22
  return None
21
23
 
22
24
 
23
- class NodeStoreBase:
24
- """Internal Store for InfrahubNode objects.
25
-
26
- Often while creating a lot of new objects,
27
- we need to save them in order to reuse them later to associate them with another node for example.
28
- """
25
+ class NodeStoreBranch:
26
+ def __init__(self, name: str) -> None:
27
+ self.branch_name = name
29
28
 
30
- def __init__(self) -> None:
31
- self._store: dict[str, dict] = defaultdict(dict)
32
- self._store_by_hfid: dict[str, Any] = defaultdict(dict)
29
+ self._objs: dict[str, InfrahubNode | InfrahubNodeSync | CoreNode | CoreNodeSync] = {}
30
+ self._hfids: dict[str, dict[tuple, str]] = {}
31
+ self._keys: dict[str, str] = {}
32
+ self._uuids: dict[str, str] = {}
33
33
 
34
- def _set(self, node: InfrahubNode | InfrahubNodeSync | SchemaType, key: str | None = None) -> None:
35
- hfid = node.get_human_friendly_id_as_string(include_kind=True)
34
+ def count(self) -> int:
35
+ return len(self._objs)
36
36
 
37
- if not key and not hfid:
38
- raise ValueError("Cannot store node without human friendly ID or key.")
37
+ def set(self, node: InfrahubNode | InfrahubNodeSync | CoreNode | CoreNodeSync, key: str | None = None) -> None:
38
+ self._objs[node._internal_id] = node
39
39
 
40
40
  if key:
41
- node_kind = node._schema.kind
42
- self._store[node_kind][key] = node
41
+ self._keys[key] = node._internal_id
43
42
 
44
- if hfid:
45
- self._store_by_hfid[hfid] = node
43
+ if node.id:
44
+ self._uuids[node.id] = node._internal_id
45
+
46
+ if hfid := node.get_human_friendly_id():
47
+ for kind in node.get_all_kinds():
48
+ if kind not in self._hfids:
49
+ self._hfids[kind] = {}
50
+ self._hfids[kind][tuple(hfid)] = node._internal_id
51
+
52
+ def get(
53
+ self,
54
+ key: str | list[str],
55
+ kind: type[SchemaType | SchemaTypeSync] | str | None = None,
56
+ raise_when_missing: bool = True,
57
+ ) -> InfrahubNode | InfrahubNodeSync | CoreNode | CoreNodeSync | None:
58
+ found_invalid = False
46
59
 
47
- def _get(self, key: str, kind: str | type[SchemaType] | None = None, raise_when_missing: bool = True): # type: ignore[no-untyped-def]
48
60
  kind_name = get_schema_name(schema=kind)
49
- if kind_name and kind_name not in self._store and key not in self._store[kind_name]: # type: ignore[attr-defined]
50
- if not raise_when_missing:
51
- return None
61
+
62
+ if isinstance(key, list):
63
+ try:
64
+ return self._get_by_hfid(key, kind=kind_name)
65
+ except NodeNotFoundError:
66
+ pass
67
+
68
+ elif isinstance(key, str):
69
+ try:
70
+ return self._get_by_internal_id(key, kind=kind_name)
71
+ except NodeInvalidError:
72
+ found_invalid = True
73
+ except NodeNotFoundError:
74
+ pass
75
+
76
+ try:
77
+ return self._get_by_id(key, kind=kind_name)
78
+ except NodeInvalidError:
79
+ found_invalid = True
80
+ except NodeNotFoundError:
81
+ pass
82
+
83
+ try:
84
+ return self._get_by_key(key, kind=kind_name)
85
+ except NodeInvalidError:
86
+ found_invalid = True
87
+ except NodeNotFoundError:
88
+ pass
89
+
90
+ try:
91
+ return self._get_by_hfid(key, kind=kind_name)
92
+ except NodeNotFoundError:
93
+ pass
94
+
95
+ if not raise_when_missing:
96
+ return None
97
+
98
+ if kind and found_invalid:
99
+ raise NodeInvalidError(
100
+ identifier={"key": [key] if isinstance(key, str) else key},
101
+ message=f"Found a node of a different kind instead of {kind} for key {key!r} in the store ({self.branch_name})",
102
+ )
103
+
104
+ raise NodeNotFoundError(
105
+ identifier={"key": [key] if isinstance(key, str) else key},
106
+ message=f"Unable to find the node {key!r} in the store ({self.branch_name})",
107
+ )
108
+
109
+ def _get_by_internal_id(
110
+ self, internal_id: str, kind: str | None = None
111
+ ) -> InfrahubNode | InfrahubNodeSync | CoreNode | CoreNodeSync:
112
+ if internal_id not in self._objs:
113
+ raise NodeNotFoundError(
114
+ identifier={"internal_id": [internal_id]},
115
+ message=f"Unable to find the node {internal_id!r} in the store ({self.branch_name})",
116
+ )
117
+
118
+ node = self._objs[internal_id]
119
+ if kind and kind not in node.get_all_kinds():
120
+ raise NodeInvalidError(
121
+ node_type=kind,
122
+ identifier={"internal_id": [internal_id]},
123
+ message=f"Found a node of kind {node.get_kind()} instead of {kind} for internal_id {internal_id!r} in the store ({self.branch_name})",
124
+ )
125
+
126
+ return node
127
+
128
+ def _get_by_key(
129
+ self, key: str, kind: str | None = None
130
+ ) -> InfrahubNode | InfrahubNodeSync | CoreNode | CoreNodeSync:
131
+ if key not in self._keys:
52
132
  raise NodeNotFoundError(
53
- node_type=kind_name,
54
133
  identifier={"key": [key]},
55
- message="Unable to find the node in the Store",
134
+ message=f"Unable to find the node {key!r} in the store ({self.branch_name})",
56
135
  )
57
136
 
58
- if kind_name and kind_name in self._store and key in self._store[kind_name]: # type: ignore[attr-defined]
59
- return self._store[kind_name][key] # type: ignore[attr-defined]
137
+ node = self._get_by_internal_id(self._keys[key])
60
138
 
61
- for item in self._store.values(): # type: ignore[attr-defined]
62
- if key in item:
63
- return item[key]
139
+ if kind and node.get_kind() != kind:
140
+ raise NodeInvalidError(
141
+ node_type=kind,
142
+ identifier={"key": [key]},
143
+ message=f"Found a node of kind {node.get_kind()} instead of {kind} for key {key!r} in the store ({self.branch_name})",
144
+ )
64
145
 
65
- if not raise_when_missing:
66
- return None
67
- raise NodeNotFoundError(
68
- node_type="n/a",
69
- identifier={"key": [key]},
70
- message=f"Unable to find the node {key!r} in the store",
146
+ return node
147
+
148
+ def _get_by_id(self, id: str, kind: str | None = None) -> InfrahubNode | InfrahubNodeSync | CoreNode | CoreNodeSync:
149
+ if id not in self._uuids:
150
+ raise NodeNotFoundError(
151
+ identifier={"id": [id]},
152
+ message=f"Unable to find the node {id!r} in the store ({self.branch_name})",
153
+ )
154
+
155
+ node = self._get_by_internal_id(self._uuids[id])
156
+ if kind and kind not in node.get_all_kinds():
157
+ raise NodeInvalidError(
158
+ node_type=kind,
159
+ identifier={"id": [id]},
160
+ message=f"Found a node of kind {node.get_kind()} instead of {kind} for id {id!r} in the store ({self.branch_name})",
161
+ )
162
+
163
+ return node
164
+
165
+ def _get_by_hfid(
166
+ self, hfid: str | list[str], kind: str | None = None
167
+ ) -> InfrahubNode | InfrahubNodeSync | CoreNode | CoreNodeSync:
168
+ if not kind:
169
+ node_kind, node_hfid = parse_human_friendly_id(hfid)
170
+ elif kind and isinstance(hfid, str) and hfid.startswith(kind):
171
+ node_kind, node_hfid = parse_human_friendly_id(hfid)
172
+ else:
173
+ node_kind = kind
174
+ node_hfid = [hfid] if isinstance(hfid, str) else hfid
175
+
176
+ exception_to_raise_if_not_found = NodeNotFoundError(
177
+ node_type=node_kind,
178
+ identifier={"hfid": node_hfid},
179
+ message=f"Unable to find the node {hfid!r} in the store ({self.branch_name})",
71
180
  )
72
181
 
73
- def _get_by_hfid(self, key: str, raise_when_missing: bool = True): # type: ignore[no-untyped-def]
74
- try:
75
- return self._store_by_hfid[key]
76
- except KeyError as exc:
77
- if raise_when_missing:
78
- raise NodeNotFoundError(
79
- node_type="n/a",
80
- identifier={"key": [key]},
81
- message=f"Unable to find the node {key!r} in the store",
82
- ) from exc
83
- return None
182
+ if node_kind not in self._hfids:
183
+ raise exception_to_raise_if_not_found
184
+
185
+ if tuple(node_hfid) not in self._hfids[node_kind]:
186
+ raise exception_to_raise_if_not_found
187
+
188
+ internal_id = self._hfids[node_kind][tuple(node_hfid)]
189
+ return self._objs[internal_id]
190
+
191
+
192
+ class NodeStoreBase:
193
+ """Internal Store for InfrahubNode objects.
194
+
195
+ Often while creating a lot of new objects,
196
+ we need to save them in order to reuse them later to associate them with another node for example.
197
+ """
198
+
199
+ def __init__(self, default_branch: str | None = None) -> None:
200
+ self._branches: dict[str, NodeStoreBranch] = {}
201
+
202
+ if default_branch is None:
203
+ default_branch = "main"
204
+ warnings.warn(
205
+ "Using a store without specifying a default branch is deprecated and will be removed in a future version. "
206
+ "Please explicitly specify a branch name.",
207
+ DeprecationWarning,
208
+ stacklevel=2,
209
+ )
210
+
211
+ self._default_branch = default_branch
212
+
213
+ def _get_branch(self, branch: str | None = None) -> str:
214
+ return branch or self._default_branch
215
+
216
+ def _set(
217
+ self,
218
+ node: InfrahubNode | InfrahubNodeSync | SchemaType | SchemaTypeSync,
219
+ key: str | None = None,
220
+ branch: str | None = None,
221
+ ) -> None:
222
+ branch = self._get_branch(branch or node.get_branch())
223
+
224
+ if branch not in self._branches:
225
+ self._branches[branch] = NodeStoreBranch(name=branch)
226
+
227
+ self._branches[branch].set(node=node, key=key)
228
+
229
+ def _get( # type: ignore[no-untyped-def]
230
+ self,
231
+ key: str | list[str],
232
+ kind: type[SchemaType | SchemaTypeSync] | str | None = None,
233
+ raise_when_missing: bool = True,
234
+ branch: str | None = None,
235
+ ):
236
+ branch = self._get_branch(branch)
237
+
238
+ if branch not in self._branches:
239
+ self._branches[branch] = NodeStoreBranch(name=branch)
240
+
241
+ return self._branches[branch].get(key=key, kind=kind, raise_when_missing=raise_when_missing)
242
+
243
+ def count(self, branch: str | None = None) -> int:
244
+ branch = self._get_branch(branch)
245
+
246
+ if branch not in self._branches:
247
+ return 0
248
+
249
+ return self._branches[branch].count()
84
250
 
85
251
 
86
252
  class NodeStore(NodeStoreBase):
87
253
  @overload
88
- def get(self, key: str, kind: type[SchemaType], raise_when_missing: Literal[True] = True) -> SchemaType: ...
254
+ def get(
255
+ self,
256
+ key: str | list[str],
257
+ kind: type[SchemaType],
258
+ raise_when_missing: Literal[True] = True,
259
+ branch: str | None = ...,
260
+ ) -> SchemaType: ...
89
261
 
90
262
  @overload
91
263
  def get(
92
- self, key: str, kind: type[SchemaType], raise_when_missing: Literal[False] = False
264
+ self,
265
+ key: str | list[str],
266
+ kind: type[SchemaType],
267
+ raise_when_missing: Literal[False] = False,
268
+ branch: str | None = ...,
93
269
  ) -> SchemaType | None: ...
94
270
 
95
271
  @overload
96
- def get(self, key: str, kind: type[SchemaType], raise_when_missing: bool = ...) -> SchemaType: ...
272
+ def get(
273
+ self,
274
+ key: str | list[str],
275
+ kind: type[SchemaType],
276
+ raise_when_missing: bool = ...,
277
+ branch: str | None = ...,
278
+ ) -> SchemaType: ...
97
279
 
98
280
  @overload
99
281
  def get(
100
- self, key: str, kind: str | None = ..., raise_when_missing: Literal[False] = False
101
- ) -> InfrahubNode | None: ...
282
+ self,
283
+ key: str | list[str],
284
+ kind: str | None = ...,
285
+ raise_when_missing: Literal[True] = True,
286
+ branch: str | None = ...,
287
+ ) -> InfrahubNode: ...
102
288
 
103
289
  @overload
104
- def get(self, key: str, kind: str | None = ..., raise_when_missing: Literal[True] = True) -> InfrahubNode: ...
290
+ def get(
291
+ self,
292
+ key: str | list[str],
293
+ kind: str | None = ...,
294
+ raise_when_missing: Literal[False] = False,
295
+ branch: str | None = ...,
296
+ ) -> InfrahubNode | None: ...
105
297
 
106
298
  @overload
107
- def get(self, key: str, kind: str | None = ..., raise_when_missing: bool = ...) -> InfrahubNode: ...
299
+ def get(
300
+ self,
301
+ key: str | list[str],
302
+ kind: str | None = ...,
303
+ raise_when_missing: bool = ...,
304
+ branch: str | None = ...,
305
+ ) -> InfrahubNode: ...
108
306
 
109
307
  def get(
110
- self, key: str, kind: str | type[SchemaType] | None = None, raise_when_missing: bool = True
308
+ self,
309
+ key: str | list[str],
310
+ kind: str | type[SchemaType] | None = None,
311
+ raise_when_missing: bool = True,
312
+ branch: str | None = None,
111
313
  ) -> InfrahubNode | SchemaType | None:
112
- return self._get(key=key, kind=kind, raise_when_missing=raise_when_missing)
314
+ return self._get(key=key, kind=kind, raise_when_missing=raise_when_missing, branch=branch)
113
315
 
114
316
  @overload
115
- def get_by_hfid(self, key: str, raise_when_missing: Literal[True] = True) -> InfrahubNode: ...
317
+ def get_by_hfid(
318
+ self, key: str | list[str], raise_when_missing: Literal[True] = True, branch: str | None = ...
319
+ ) -> InfrahubNode: ...
116
320
 
117
321
  @overload
118
- def get_by_hfid(self, key: str, raise_when_missing: Literal[False] = False) -> InfrahubNode | None: ...
322
+ def get_by_hfid(
323
+ self, key: str | list[str], raise_when_missing: Literal[False] = False, branch: str | None = ...
324
+ ) -> InfrahubNode | None: ...
119
325
 
120
- def get_by_hfid(self, key: str, raise_when_missing: bool = True) -> InfrahubNode | None:
121
- return self._get_by_hfid(key=key, raise_when_missing=raise_when_missing)
326
+ def get_by_hfid(
327
+ self, key: str | list[str], raise_when_missing: bool = True, branch: str | None = None
328
+ ) -> InfrahubNode | None:
329
+ warnings.warn(
330
+ "get_by_hfid() is deprecated and will be removed in a future version. Use get() instead.",
331
+ DeprecationWarning,
332
+ stacklevel=2,
333
+ )
334
+ return self.get(key=key, raise_when_missing=raise_when_missing, branch=branch)
122
335
 
123
- def set(self, node: Any, key: str | None = None) -> None:
124
- return self._set(node=node, key=key)
336
+ def set(self, node: InfrahubNode | SchemaType, key: str | None = None, branch: str | None = None) -> None:
337
+ return self._set(node=node, key=key, branch=branch)
125
338
 
126
339
 
127
340
  class NodeStoreSync(NodeStoreBase):
128
341
  @overload
129
- def get(self, key: str, kind: str | None = None, raise_when_missing: Literal[True] = True) -> InfrahubNodeSync: ...
342
+ def get(
343
+ self,
344
+ key: str | list[str],
345
+ kind: type[SchemaTypeSync],
346
+ raise_when_missing: Literal[True] = True,
347
+ branch: str | None = ...,
348
+ ) -> SchemaTypeSync: ...
130
349
 
131
350
  @overload
132
351
  def get(
133
- self, key: str, kind: str | None = None, raise_when_missing: Literal[False] = False
352
+ self,
353
+ key: str | list[str],
354
+ kind: type[SchemaTypeSync],
355
+ raise_when_missing: Literal[False] = False,
356
+ branch: str | None = ...,
357
+ ) -> SchemaTypeSync | None: ...
358
+
359
+ @overload
360
+ def get(
361
+ self,
362
+ key: str | list[str],
363
+ kind: type[SchemaTypeSync],
364
+ raise_when_missing: bool = ...,
365
+ branch: str | None = ...,
366
+ ) -> SchemaTypeSync: ...
367
+
368
+ @overload
369
+ def get(
370
+ self,
371
+ key: str | list[str],
372
+ kind: str | None = ...,
373
+ raise_when_missing: Literal[True] = True,
374
+ branch: str | None = ...,
375
+ ) -> InfrahubNodeSync: ...
376
+
377
+ @overload
378
+ def get(
379
+ self,
380
+ key: str | list[str],
381
+ kind: str | None = ...,
382
+ raise_when_missing: Literal[False] = False,
383
+ branch: str | None = ...,
134
384
  ) -> InfrahubNodeSync | None: ...
135
385
 
136
- def get(self, key: str, kind: str | None = None, raise_when_missing: bool = True) -> InfrahubNodeSync | None:
137
- return self._get(key=key, kind=kind, raise_when_missing=raise_when_missing)
386
+ @overload
387
+ def get(
388
+ self,
389
+ key: str | list[str],
390
+ kind: str | None = ...,
391
+ raise_when_missing: bool = ...,
392
+ branch: str | None = ...,
393
+ ) -> InfrahubNodeSync: ...
394
+
395
+ def get(
396
+ self,
397
+ key: str | list[str],
398
+ kind: str | type[SchemaTypeSync] | None = None,
399
+ raise_when_missing: bool = True,
400
+ branch: str | None = None,
401
+ ) -> InfrahubNodeSync | SchemaTypeSync | None:
402
+ return self._get(key=key, kind=kind, raise_when_missing=raise_when_missing, branch=branch)
138
403
 
139
404
  @overload
140
- def get_by_hfid(self, key: str, raise_when_missing: Literal[True] = True) -> InfrahubNodeSync: ...
405
+ def get_by_hfid(
406
+ self, key: str | list[str], raise_when_missing: Literal[True] = True, branch: str | None = ...
407
+ ) -> InfrahubNodeSync: ...
141
408
 
142
409
  @overload
143
- def get_by_hfid(self, key: str, raise_when_missing: Literal[False] = False) -> InfrahubNodeSync | None: ...
410
+ def get_by_hfid(
411
+ self, key: str | list[str], raise_when_missing: Literal[False] = False, branch: str | None = ...
412
+ ) -> InfrahubNodeSync | None: ...
144
413
 
145
- def get_by_hfid(self, key: str, raise_when_missing: bool = True) -> InfrahubNodeSync | None:
146
- return self._get_by_hfid(key=key, raise_when_missing=raise_when_missing)
414
+ def get_by_hfid(
415
+ self, key: str | list[str], raise_when_missing: bool = True, branch: str | None = None
416
+ ) -> InfrahubNodeSync | None:
417
+ warnings.warn(
418
+ "get_by_hfid() is deprecated and will be removed in a future version. Use get() instead.",
419
+ DeprecationWarning,
420
+ stacklevel=2,
421
+ )
422
+ return self.get(key=key, raise_when_missing=raise_when_missing, branch=branch)
147
423
 
148
- def set(self, node: InfrahubNodeSync, key: str | None = None) -> None:
149
- return self._set(node=node, key=key)
424
+ def set(self, node: InfrahubNodeSync | SchemaTypeSync, key: str | None = None, branch: str | None = None) -> None:
425
+ return self._set(node=node, key=key, branch=branch)