infrahub-server 1.7.0b0__py3-none-any.whl → 1.7.1__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 (177) hide show
  1. infrahub/api/exceptions.py +2 -2
  2. infrahub/api/schema.py +5 -0
  3. infrahub/cli/db.py +54 -24
  4. infrahub/core/account.py +12 -9
  5. infrahub/core/branch/models.py +11 -117
  6. infrahub/core/branch/tasks.py +7 -3
  7. infrahub/core/diff/branch_differ.py +1 -1
  8. infrahub/core/diff/conflict_transferer.py +1 -1
  9. infrahub/core/diff/data_check_synchronizer.py +1 -1
  10. infrahub/core/diff/enricher/cardinality_one.py +1 -1
  11. infrahub/core/diff/enricher/hierarchy.py +1 -1
  12. infrahub/core/diff/enricher/labels.py +1 -1
  13. infrahub/core/diff/merger/merger.py +6 -2
  14. infrahub/core/diff/repository/repository.py +3 -1
  15. infrahub/core/graph/__init__.py +1 -1
  16. infrahub/core/graph/constraints.py +1 -1
  17. infrahub/core/initialization.py +2 -1
  18. infrahub/core/ipam/reconciler.py +8 -6
  19. infrahub/core/ipam/utilization.py +8 -15
  20. infrahub/core/manager.py +1 -26
  21. infrahub/core/merge.py +1 -1
  22. infrahub/core/migrations/graph/__init__.py +2 -0
  23. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -12
  24. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +4 -4
  25. infrahub/core/migrations/graph/m014_remove_index_attr_value.py +3 -2
  26. infrahub/core/migrations/graph/m015_diff_format_update.py +3 -2
  27. infrahub/core/migrations/graph/m016_diff_delete_bug_fix.py +3 -2
  28. infrahub/core/migrations/graph/m017_add_core_profile.py +6 -4
  29. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +3 -4
  30. infrahub/core/migrations/graph/m020_duplicate_edges.py +3 -3
  31. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +3 -4
  32. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +4 -5
  33. infrahub/core/migrations/graph/m028_delete_diffs.py +3 -2
  34. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +3 -2
  35. infrahub/core/migrations/graph/m031_check_number_attributes.py +4 -3
  36. infrahub/core/migrations/graph/m032_cleanup_orphaned_branch_relationships.py +3 -2
  37. infrahub/core/migrations/graph/m034_find_orphaned_schema_fields.py +3 -2
  38. infrahub/core/migrations/graph/m035_orphan_relationships.py +3 -3
  39. infrahub/core/migrations/graph/m036_drop_attr_value_index.py +3 -2
  40. infrahub/core/migrations/graph/m037_index_attr_vals.py +3 -2
  41. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +4 -5
  42. infrahub/core/migrations/graph/m039_ipam_reconcile.py +3 -2
  43. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +4 -3
  44. infrahub/core/migrations/graph/m042_profile_attrs_in_db.py +5 -4
  45. infrahub/core/migrations/graph/m043_create_hfid_display_label_in_db.py +12 -5
  46. infrahub/core/migrations/graph/m044_backfill_hfid_display_label_in_db.py +15 -4
  47. infrahub/core/migrations/graph/m045_backfill_hfid_display_label_in_db_profile_template.py +10 -4
  48. infrahub/core/migrations/graph/m046_fill_agnostic_hfid_display_labels.py +6 -5
  49. infrahub/core/migrations/graph/m047_backfill_or_null_display_label.py +19 -5
  50. infrahub/core/migrations/graph/m048_undelete_rel_props.py +6 -4
  51. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +19 -4
  52. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +3 -3
  53. infrahub/core/migrations/graph/m051_subtract_branched_from_microsecond.py +39 -0
  54. infrahub/core/migrations/query/__init__.py +2 -2
  55. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  56. infrahub/core/migrations/runner.py +6 -3
  57. infrahub/core/migrations/schema/attribute_kind_update.py +8 -11
  58. infrahub/core/migrations/schema/attribute_name_update.py +1 -1
  59. infrahub/core/migrations/schema/attribute_supports_profile.py +5 -10
  60. infrahub/core/migrations/schema/models.py +8 -0
  61. infrahub/core/migrations/schema/node_attribute_add.py +11 -14
  62. infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
  63. infrahub/core/migrations/schema/node_kind_update.py +1 -1
  64. infrahub/core/migrations/schema/tasks.py +7 -1
  65. infrahub/core/migrations/shared.py +37 -30
  66. infrahub/core/node/__init__.py +3 -2
  67. infrahub/core/node/base.py +9 -5
  68. infrahub/core/node/delete_validator.py +1 -1
  69. infrahub/core/order.py +30 -0
  70. infrahub/core/protocols.py +1 -0
  71. infrahub/core/protocols_base.py +4 -0
  72. infrahub/core/query/__init__.py +8 -5
  73. infrahub/core/query/attribute.py +3 -3
  74. infrahub/core/query/branch.py +1 -1
  75. infrahub/core/query/delete.py +1 -1
  76. infrahub/core/query/diff.py +3 -3
  77. infrahub/core/query/ipam.py +104 -43
  78. infrahub/core/query/node.py +454 -101
  79. infrahub/core/query/relationship.py +83 -26
  80. infrahub/core/query/resource_manager.py +107 -18
  81. infrahub/core/relationship/constraints/count.py +1 -1
  82. infrahub/core/relationship/constraints/peer_kind.py +1 -1
  83. infrahub/core/relationship/constraints/peer_parent.py +1 -1
  84. infrahub/core/relationship/constraints/peer_relatives.py +1 -1
  85. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  86. infrahub/core/relationship/constraints/profiles_removal.py +1 -1
  87. infrahub/core/relationship/model.py +8 -2
  88. infrahub/core/schema/attribute_parameters.py +28 -1
  89. infrahub/core/schema/attribute_schema.py +9 -15
  90. infrahub/core/schema/basenode_schema.py +3 -0
  91. infrahub/core/schema/definitions/core/__init__.py +8 -2
  92. infrahub/core/schema/definitions/core/account.py +10 -10
  93. infrahub/core/schema/definitions/core/artifact.py +14 -8
  94. infrahub/core/schema/definitions/core/check.py +10 -4
  95. infrahub/core/schema/definitions/core/generator.py +26 -6
  96. infrahub/core/schema/definitions/core/graphql_query.py +1 -1
  97. infrahub/core/schema/definitions/core/group.py +9 -2
  98. infrahub/core/schema/definitions/core/ipam.py +80 -10
  99. infrahub/core/schema/definitions/core/menu.py +41 -7
  100. infrahub/core/schema/definitions/core/permission.py +16 -2
  101. infrahub/core/schema/definitions/core/profile.py +16 -2
  102. infrahub/core/schema/definitions/core/propose_change.py +24 -4
  103. infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
  104. infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
  105. infrahub/core/schema/definitions/core/repository.py +10 -0
  106. infrahub/core/schema/definitions/core/resource_pool.py +8 -1
  107. infrahub/core/schema/definitions/core/template.py +19 -2
  108. infrahub/core/schema/definitions/core/transform.py +11 -5
  109. infrahub/core/schema/definitions/core/webhook.py +27 -9
  110. infrahub/core/schema/manager.py +50 -38
  111. infrahub/core/schema/schema_branch.py +68 -2
  112. infrahub/core/utils.py +3 -3
  113. infrahub/core/validators/aggregated_checker.py +1 -1
  114. infrahub/core/validators/attribute/choices.py +1 -1
  115. infrahub/core/validators/attribute/enum.py +1 -1
  116. infrahub/core/validators/attribute/kind.py +6 -3
  117. infrahub/core/validators/attribute/length.py +1 -1
  118. infrahub/core/validators/attribute/min_max.py +1 -1
  119. infrahub/core/validators/attribute/number_pool.py +1 -1
  120. infrahub/core/validators/attribute/optional.py +1 -1
  121. infrahub/core/validators/attribute/regex.py +1 -1
  122. infrahub/core/validators/node/attribute.py +1 -1
  123. infrahub/core/validators/node/relationship.py +1 -1
  124. infrahub/core/validators/relationship/peer.py +1 -1
  125. infrahub/database/__init__.py +1 -1
  126. infrahub/git/utils.py +1 -1
  127. infrahub/graphql/app.py +2 -2
  128. infrahub/graphql/field_extractor.py +1 -1
  129. infrahub/graphql/manager.py +17 -3
  130. infrahub/graphql/mutations/account.py +1 -1
  131. infrahub/graphql/order.py +14 -0
  132. infrahub/graphql/queries/diff/tree.py +5 -5
  133. infrahub/graphql/queries/resource_manager.py +25 -24
  134. infrahub/graphql/resolvers/ipam.py +3 -3
  135. infrahub/graphql/resolvers/resolver.py +44 -3
  136. infrahub/graphql/types/standard_node.py +8 -4
  137. infrahub/lock.py +7 -0
  138. infrahub/menu/repository.py +1 -1
  139. infrahub/patch/queries/base.py +1 -1
  140. infrahub/pools/number.py +1 -8
  141. infrahub/profiles/node_applier.py +1 -1
  142. infrahub/profiles/queries/get_profile_data.py +1 -1
  143. infrahub/proposed_change/action_checker.py +1 -1
  144. infrahub/services/__init__.py +1 -1
  145. infrahub/services/adapters/cache/nats.py +1 -1
  146. infrahub/services/adapters/cache/redis.py +7 -0
  147. infrahub/webhook/gather.py +1 -1
  148. infrahub/webhook/tasks.py +22 -6
  149. infrahub_sdk/analyzer.py +2 -2
  150. infrahub_sdk/branch.py +12 -39
  151. infrahub_sdk/checks.py +4 -4
  152. infrahub_sdk/client.py +36 -0
  153. infrahub_sdk/ctl/cli_commands.py +2 -1
  154. infrahub_sdk/ctl/graphql.py +15 -4
  155. infrahub_sdk/ctl/utils.py +2 -2
  156. infrahub_sdk/enums.py +6 -0
  157. infrahub_sdk/graphql/renderers.py +21 -0
  158. infrahub_sdk/graphql/utils.py +85 -0
  159. infrahub_sdk/node/attribute.py +12 -2
  160. infrahub_sdk/node/constants.py +11 -0
  161. infrahub_sdk/node/metadata.py +69 -0
  162. infrahub_sdk/node/node.py +65 -14
  163. infrahub_sdk/node/property.py +3 -0
  164. infrahub_sdk/node/related_node.py +24 -1
  165. infrahub_sdk/node/relationship.py +10 -1
  166. infrahub_sdk/operation.py +2 -2
  167. infrahub_sdk/schema/repository.py +1 -2
  168. infrahub_sdk/transforms.py +2 -2
  169. infrahub_sdk/types.py +18 -2
  170. {infrahub_server-1.7.0b0.dist-info → infrahub_server-1.7.1.dist-info}/METADATA +6 -6
  171. {infrahub_server-1.7.0b0.dist-info → infrahub_server-1.7.1.dist-info}/RECORD +176 -172
  172. {infrahub_server-1.7.0b0.dist-info → infrahub_server-1.7.1.dist-info}/entry_points.txt +0 -1
  173. infrahub_testcontainers/models.py +3 -3
  174. infrahub_testcontainers/performance_test.py +1 -1
  175. infrahub/graphql/models.py +0 -36
  176. {infrahub_server-1.7.0b0.dist-info → infrahub_server-1.7.1.dist-info}/WHEEL +0 -0
  177. {infrahub_server-1.7.0b0.dist-info → infrahub_server-1.7.1.dist-info}/licenses/LICENSE.txt +0 -0
@@ -25,6 +25,7 @@ from infrahub.core.schema import (
25
25
  SchemaRoot,
26
26
  TemplateSchema,
27
27
  )
28
+ from infrahub.core.timestamp import Timestamp
28
29
  from infrahub.core.utils import parse_node_kind
29
30
  from infrahub.exceptions import SchemaNotFoundError
30
31
  from infrahub.log import get_logger
@@ -36,7 +37,6 @@ log = get_logger()
36
37
 
37
38
  if TYPE_CHECKING:
38
39
  from infrahub.core.branch import Branch
39
- from infrahub.core.timestamp import Timestamp
40
40
  from infrahub.database import InfrahubDatabase
41
41
 
42
42
 
@@ -179,18 +179,20 @@ class SchemaManager(NodeManager):
179
179
  diff: SchemaDiff | None = None,
180
180
  limit: list[str] | None = None,
181
181
  update_db: bool = True,
182
+ at: Timestamp | None = None,
182
183
  user_id: str = SYSTEM_USER_ID,
183
184
  ) -> None:
184
185
  branch = await registry.get_branch(branch=branch, db=db)
186
+ at = Timestamp(at)
185
187
 
186
188
  updated_schema = None
187
189
  if update_db:
188
190
  if diff:
189
191
  schema_diff = await self.update_schema_to_db(
190
- schema=schema, db=db, branch=branch, diff=diff, user_id=user_id
192
+ schema=schema, db=db, branch=branch, diff=diff, at=at, user_id=user_id
191
193
  )
192
194
  else:
193
- await self.load_schema_to_db(schema=schema, db=db, branch=branch, limit=limit, user_id=user_id)
195
+ await self.load_schema_to_db(schema=schema, db=db, branch=branch, limit=limit, at=at, user_id=user_id)
194
196
  # After updating the schema into the db
195
197
  # we need to pull a fresh version because some default value are managed/generated within the node object
196
198
  schema_diff = None
@@ -201,7 +203,7 @@ class SchemaManager(NodeManager):
201
203
  )
202
204
 
203
205
  updated_schema = await self.load_schema_from_db(
204
- db=db, branch=branch, schema=schema, schema_diff=schema_diff
206
+ db=db, branch=branch, schema=schema, schema_diff=schema_diff, at=at
205
207
  )
206
208
 
207
209
  self.set_schema_branch(name=branch.name, schema=updated_schema or schema)
@@ -221,6 +223,7 @@ class SchemaManager(NodeManager):
221
223
  db: InfrahubDatabase,
222
224
  diff: SchemaDiff,
223
225
  user_id: str,
226
+ at: Timestamp,
224
227
  branch: Branch | str | None = None,
225
228
  ) -> SchemaBranchDiff:
226
229
  """Load all nodes, generics and groups from a SchemaRoot object into the database."""
@@ -231,7 +234,7 @@ class SchemaManager(NodeManager):
231
234
  added_generics = []
232
235
  for item_kind in diff.added.keys():
233
236
  item = schema.get(name=item_kind, duplicate=False)
234
- node = await self.load_node_to_db(node=item, branch=branch, db=db, user_id=user_id)
237
+ node = await self.load_node_to_db(node=item, branch=branch, db=db, at=at, user_id=user_id)
235
238
  schema.set(name=item_kind, schema=node)
236
239
  if item.is_node_schema:
237
240
  added_nodes.append(item_kind)
@@ -244,10 +247,10 @@ class SchemaManager(NodeManager):
244
247
  item = schema.get(name=item_kind, duplicate=False)
245
248
  if item_diff:
246
249
  node = await self.update_node_in_db_based_on_diff(
247
- node=item, branch=branch, db=db, diff=item_diff, user_id=user_id
250
+ node=item, branch=branch, db=db, diff=item_diff, at=at, user_id=user_id
248
251
  )
249
252
  else:
250
- node = await self.update_node_in_db(node=item, branch=branch, db=db, user_id=user_id)
253
+ node = await self.update_node_in_db(node=item, branch=branch, db=db, at=at, user_id=user_id)
251
254
  schema.set(name=item_kind, schema=node)
252
255
  if item.is_node_schema:
253
256
  changed_nodes.append(item_kind)
@@ -258,7 +261,7 @@ class SchemaManager(NodeManager):
258
261
  removed_generics = []
259
262
  for item_kind in diff.removed.keys():
260
263
  item = schema.get(name=item_kind, duplicate=False)
261
- node = await self.delete_node_in_db(node=item, branch=branch, db=db, user_id=user_id)
264
+ node = await self.delete_node_in_db(node=item, branch=branch, db=db, at=at, user_id=user_id)
262
265
  schema.delete(name=item_kind)
263
266
  if item.is_node_schema:
264
267
  removed_nodes.append(item_kind)
@@ -281,9 +284,10 @@ class SchemaManager(NodeManager):
281
284
  branch: Branch | str | None = None,
282
285
  limit: list[str] | None = None,
283
286
  user_id: str = SYSTEM_USER_ID,
287
+ at: Timestamp | None = None,
284
288
  ) -> None:
285
289
  """Load all nodes, generics and groups from a SchemaRoot object into the database."""
286
-
290
+ at = Timestamp(at)
287
291
  branch = await registry.get_branch(branch=branch, db=db)
288
292
 
289
293
  for item_kind in schema.node_names + schema.generic_names_without_templates:
@@ -291,10 +295,10 @@ class SchemaManager(NodeManager):
291
295
  continue
292
296
  item = schema.get(name=item_kind, duplicate=False)
293
297
  if not item.id:
294
- node = await self.load_node_to_db(node=item, branch=branch, db=db, user_id=user_id)
298
+ node = await self.load_node_to_db(node=item, branch=branch, db=db, at=at, user_id=user_id)
295
299
  schema.set(name=item_kind, schema=node)
296
300
  else:
297
- node = await self.update_node_in_db(node=item, branch=branch, db=db, user_id=user_id)
301
+ node = await self.update_node_in_db(node=item, branch=branch, db=db, at=at, user_id=user_id)
298
302
  schema.set(name=item_kind, schema=node)
299
303
 
300
304
  async def load_node_to_db(
@@ -302,6 +306,7 @@ class SchemaManager(NodeManager):
302
306
  node: NodeSchema | GenericSchema,
303
307
  db: InfrahubDatabase,
304
308
  user_id: str,
309
+ at: Timestamp,
305
310
  branch: Branch | str | None = None,
306
311
  ) -> NodeSchema | GenericSchema:
307
312
  """Load a Node with its attributes and its relationships to the database."""
@@ -322,7 +327,7 @@ class SchemaManager(NodeManager):
322
327
  schema_dict = node.model_dump(exclude={"id", "state", "filters", "relationships", "attributes"})
323
328
  obj = await Node.init(schema=node_schema, branch=branch, db=db)
324
329
  await obj.new(**schema_dict, db=db)
325
- await obj.save(db=db, user_id=user_id)
330
+ await obj.save(db=db, at=at, user_id=user_id)
326
331
  new_node.id = obj.id
327
332
 
328
333
  # Then create the Attributes and the relationships
@@ -333,7 +338,7 @@ class SchemaManager(NodeManager):
333
338
  for item in node.attributes:
334
339
  if item.inherited is False:
335
340
  new_attr = await self.create_attribute_in_db(
336
- schema=attribute_schema, item=item, parent=obj, branch=branch, db=db, user_id=user_id
341
+ schema=attribute_schema, item=item, parent=obj, branch=branch, db=db, at=at, user_id=user_id
337
342
  )
338
343
  else:
339
344
  new_attr = item.duplicate()
@@ -342,7 +347,7 @@ class SchemaManager(NodeManager):
342
347
  for item in node.relationships:
343
348
  if item.inherited is False:
344
349
  new_rel = await self.create_relationship_in_db(
345
- schema=relationship_schema, item=item, parent=obj, branch=branch, db=db, user_id=user_id
350
+ schema=relationship_schema, item=item, parent=obj, branch=branch, db=db, at=at, user_id=user_id
346
351
  )
347
352
  else:
348
353
  new_rel = item.duplicate()
@@ -357,6 +362,7 @@ class SchemaManager(NodeManager):
357
362
  db: InfrahubDatabase,
358
363
  node: NodeSchema | GenericSchema,
359
364
  user_id: str,
365
+ at: Timestamp,
360
366
  branch: Branch | str | None = None,
361
367
  ) -> NodeSchema | GenericSchema:
362
368
  """Update a Node with its attributes and its relationships in the database."""
@@ -380,11 +386,11 @@ class SchemaManager(NodeManager):
380
386
  new_node = node.duplicate()
381
387
 
382
388
  # Update the attributes and the relationships nodes as well
383
- await obj.attributes.update(db=db, data=[item.id for item in node.local_attributes if item.id])
389
+ await obj.attributes.update(db=db, data=[item.id for item in node.local_attributes if item.id], at=at)
384
390
  await obj.relationships.update(
385
- db=db, data=[item.id for item in node.local_relationships if item.id and item.name != "profiles"]
391
+ db=db, data=[item.id for item in node.local_relationships if item.id and item.name != "profiles"], at=at
386
392
  )
387
- await obj.save(db=db, user_id=user_id)
393
+ await obj.save(db=db, at=at, user_id=user_id)
388
394
 
389
395
  # Then Update the Attributes and the relationships
390
396
 
@@ -397,19 +403,19 @@ class SchemaManager(NodeManager):
397
403
 
398
404
  for item in node.local_attributes:
399
405
  if item.id and item.id in items:
400
- await self.update_attribute_in_db(item=item, attr=items[item.id], db=db, user_id=user_id)
406
+ await self.update_attribute_in_db(item=item, attr=items[item.id], db=db, at=at, user_id=user_id)
401
407
  elif not item.id:
402
408
  new_attr = await self.create_attribute_in_db(
403
- schema=attribute_schema, item=item, branch=branch, db=db, parent=obj, user_id=user_id
409
+ schema=attribute_schema, item=item, branch=branch, db=db, parent=obj, at=at, user_id=user_id
404
410
  )
405
411
  new_node.attributes.append(new_attr)
406
412
 
407
413
  for item in node.local_relationships:
408
414
  if item.id and item.id in items:
409
- await self.update_relationship_in_db(item=item, rel=items[item.id], db=db, user_id=user_id)
415
+ await self.update_relationship_in_db(item=item, rel=items[item.id], db=db, at=at, user_id=user_id)
410
416
  elif not item.id:
411
417
  new_rel = await self.create_relationship_in_db(
412
- schema=relationship_schema, item=item, branch=branch, db=db, parent=obj, user_id=user_id
418
+ schema=relationship_schema, item=item, branch=branch, db=db, parent=obj, at=at, user_id=user_id
413
419
  )
414
420
  new_node.relationships.append(new_rel)
415
421
 
@@ -423,6 +429,7 @@ class SchemaManager(NodeManager):
423
429
  diff: HashableModelDiff,
424
430
  node: NodeSchema | GenericSchema,
425
431
  user_id: str,
432
+ at: Timestamp,
426
433
  branch: Branch | str | None = None,
427
434
  ) -> NodeSchema | GenericSchema:
428
435
  """Update a Node with its attributes and its relationships in the database based on a HashableModelDiff."""
@@ -496,24 +503,24 @@ class SchemaManager(NodeManager):
496
503
  items.update({field.id: field for field in missing_attrs + missing_rels})
497
504
 
498
505
  if diff_attributes:
499
- await obj.attributes.update(db=db, data=[item.id for item in node.local_attributes if item.id])
506
+ await obj.attributes.update(db=db, data=[item.id for item in node.local_attributes if item.id], at=at)
500
507
 
501
508
  if diff_relationships:
502
- await obj.relationships.update(db=db, data=[item.id for item in node.local_relationships if item.id])
509
+ await obj.relationships.update(db=db, data=[item.id for item in node.local_relationships if item.id], at=at)
503
510
 
504
- await obj.save(db=db, user_id=user_id)
511
+ await obj.save(db=db, at=at, user_id=user_id)
505
512
 
506
513
  if diff_attributes:
507
514
  for item in node.local_attributes:
508
515
  # if item is in changed and has no ID, then it is being overridden from a generic and must be added
509
516
  if item.name in diff_attributes.added or (item.name in diff_attributes.changed and item.id is None):
510
517
  created_item = await self.create_attribute_in_db(
511
- schema=attribute_schema, item=item, branch=branch, db=db, parent=obj, user_id=user_id
518
+ schema=attribute_schema, item=item, branch=branch, db=db, parent=obj, at=at, user_id=user_id
512
519
  )
513
520
  new_attr = new_node.get_attribute(name=item.name)
514
521
  new_attr.id = created_item.id
515
522
  elif item.name in diff_attributes.changed and item.id and item.id in items:
516
- await self.update_attribute_in_db(item=item, attr=items[item.id], db=db, user_id=user_id)
523
+ await self.update_attribute_in_db(item=item, attr=items[item.id], db=db, at=at, user_id=user_id)
517
524
  elif item.name in diff_attributes.removed and item.id and item.id in items:
518
525
  await items[item.id].delete(db=db, user_id=user_id)
519
526
  elif (
@@ -530,12 +537,12 @@ class SchemaManager(NodeManager):
530
537
  item.name in diff_relationships.changed and item.id is None
531
538
  ):
532
539
  created_rel = await self.create_relationship_in_db(
533
- schema=relationship_schema, item=item, branch=branch, db=db, parent=obj, user_id=user_id
540
+ schema=relationship_schema, item=item, branch=branch, db=db, parent=obj, at=at, user_id=user_id
534
541
  )
535
542
  new_rel = new_node.get_relationship(name=item.name)
536
543
  new_rel.id = created_rel.id
537
544
  elif item.name in diff_relationships.changed and item.id and item.id in items:
538
- await self.update_relationship_in_db(item=item, rel=items[item.id], db=db, user_id=user_id)
545
+ await self.update_relationship_in_db(item=item, rel=items[item.id], db=db, at=at, user_id=user_id)
539
546
  elif item.name in diff_relationships.removed and item.id and item.id in items:
540
547
  await items[item.id].delete(db=db, user_id=user_id)
541
548
  elif (
@@ -555,7 +562,7 @@ class SchemaManager(NodeManager):
555
562
  if field_names_to_remove:
556
563
  for field_schema in items.values():
557
564
  if field_schema.name.value in field_names_to_remove:
558
- await field_schema.delete(db=db, user_id=user_id)
565
+ await field_schema.delete(db=db, at=at, user_id=user_id)
559
566
 
560
567
  # Save back the node with the (potentially) newly created IDs in the SchemaManager
561
568
  self.set(name=new_node.kind, schema=new_node, branch=branch.name)
@@ -566,6 +573,7 @@ class SchemaManager(NodeManager):
566
573
  db: InfrahubDatabase,
567
574
  node: NodeSchema | GenericSchema,
568
575
  user_id: str,
576
+ at: Timestamp,
569
577
  branch: Branch | str | None = None,
570
578
  ) -> None:
571
579
  """Delete the node with its attributes and relationships."""
@@ -581,11 +589,11 @@ class SchemaManager(NodeManager):
581
589
 
582
590
  # First delete the attributes and the relationships
583
591
  for attr_schema_node in (await obj.attributes.get_peers(db=db)).values():
584
- await attr_schema_node.delete(db=db, user_id=user_id)
592
+ await attr_schema_node.delete(db=db, at=at, user_id=user_id)
585
593
  for rel_schema_node in (await obj.relationships.get_peers(db=db)).values():
586
- await rel_schema_node.delete(db=db, user_id=user_id)
594
+ await rel_schema_node.delete(db=db, at=at, user_id=user_id)
587
595
 
588
- await obj.delete(db=db, user_id=user_id)
596
+ await obj.delete(db=db, at=at, user_id=user_id)
589
597
 
590
598
  @staticmethod
591
599
  async def create_attribute_in_db(
@@ -595,20 +603,23 @@ class SchemaManager(NodeManager):
595
603
  parent: Node,
596
604
  db: InfrahubDatabase,
597
605
  user_id: str,
606
+ at: Timestamp,
598
607
  ) -> AttributeSchema:
599
608
  obj = await Node.init(schema=schema, branch=branch, db=db)
600
609
  await obj.new(**item.to_node(), node=parent, db=db)
601
- await obj.save(db=db, user_id=user_id)
610
+ await obj.save(db=db, at=at, user_id=user_id)
602
611
  new_item = item.duplicate()
603
612
  new_item.id = obj.id
604
613
  return new_item
605
614
 
606
615
  @staticmethod
607
- async def update_attribute_in_db(item: AttributeSchema, attr: Node, db: InfrahubDatabase, user_id: str) -> None:
616
+ async def update_attribute_in_db(
617
+ item: AttributeSchema, attr: Node, db: InfrahubDatabase, at: Timestamp, user_id: str
618
+ ) -> None:
608
619
  item_dict = item.model_dump(exclude={"id", "state", "filters"})
609
620
  for key, value in item_dict.items():
610
621
  getattr(attr, key).value = value
611
- await attr.save(db=db, user_id=user_id)
622
+ await attr.save(db=db, at=at, user_id=user_id)
612
623
 
613
624
  @staticmethod
614
625
  async def create_relationship_in_db(
@@ -618,22 +629,23 @@ class SchemaManager(NodeManager):
618
629
  parent: Node,
619
630
  db: InfrahubDatabase,
620
631
  user_id: str,
632
+ at: Timestamp,
621
633
  ) -> RelationshipSchema:
622
634
  obj = await Node.init(schema=schema, branch=branch, db=db)
623
635
  await obj.new(**item.model_dump(exclude={"id", "state", "filters"}), node=parent, db=db)
624
- await obj.save(db=db, user_id=user_id)
636
+ await obj.save(db=db, at=at, user_id=user_id)
625
637
  new_item = item.duplicate()
626
638
  new_item.id = obj.id
627
639
  return new_item
628
640
 
629
641
  @staticmethod
630
642
  async def update_relationship_in_db(
631
- item: RelationshipSchema, rel: Node, db: InfrahubDatabase, user_id: str
643
+ item: RelationshipSchema, rel: Node, db: InfrahubDatabase, at: Timestamp, user_id: str
632
644
  ) -> None:
633
645
  item_dict = item.model_dump(exclude={"id", "state", "filters"})
634
646
  for key, value in item_dict.items():
635
647
  getattr(rel, key).value = value
636
- await rel.save(db=db, user_id=user_id)
648
+ await rel.save(db=db, at=at, user_id=user_id)
637
649
 
638
650
  async def load_schema(
639
651
  self,
@@ -54,7 +54,7 @@ from infrahub.core.schema import (
54
54
  SchemaRoot,
55
55
  TemplateSchema,
56
56
  )
57
- from infrahub.core.schema.attribute_parameters import NumberPoolParameters
57
+ from infrahub.core.schema.attribute_parameters import NumberPoolParameters, TextAttributeParameters
58
58
  from infrahub.core.schema.attribute_schema import get_attribute_schema_class_for_kind
59
59
  from infrahub.core.schema.definitions.core import core_profile_schema_definition
60
60
  from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
@@ -93,7 +93,7 @@ class SchemaBranch:
93
93
  computed_attributes: ComputedAttributes | None = None,
94
94
  display_labels: DisplayLabels | None = None,
95
95
  hfids: HFIDs | None = None,
96
- ):
96
+ ) -> None:
97
97
  self._cache: dict[str, NodeSchema | GenericSchema] = cache
98
98
  self.name: str | None = name
99
99
  self.nodes: dict[str, str] = {}
@@ -501,11 +501,76 @@ class SchemaBranch:
501
501
 
502
502
  return fields or None
503
503
 
504
+ def _text_attr_needs_reconciliation(self, attr: AttributeSchema) -> bool:
505
+ """Check if a Text attribute needs reconciliation between deprecated fields and parameters."""
506
+ if not isinstance(attr.parameters, TextAttributeParameters):
507
+ return False
508
+ return (
509
+ attr.regex != attr.parameters.regex
510
+ or attr.min_length != attr.parameters.min_length
511
+ or attr.max_length != attr.parameters.max_length
512
+ )
513
+
514
+ def _reconcile_text_attr(self, attr: AttributeSchema) -> None:
515
+ """Reconcile a single Text attribute's deprecated fields with parameters.
516
+
517
+ Parameters take precedence over deprecated top-level fields when both are set.
518
+ """
519
+ if not isinstance(attr.parameters, TextAttributeParameters):
520
+ return
521
+
522
+ # Sync regex: parameters takes precedence
523
+ if attr.parameters.regex is not None:
524
+ attr.regex = attr.parameters.regex
525
+ elif attr.regex is not None:
526
+ attr.parameters.regex = attr.regex
527
+
528
+ # Sync min_length: parameters takes precedence
529
+ if attr.parameters.min_length is not None:
530
+ attr.min_length = attr.parameters.min_length
531
+ elif attr.min_length is not None:
532
+ attr.parameters.min_length = attr.min_length
533
+
534
+ # Sync max_length: parameters takes precedence
535
+ if attr.parameters.max_length is not None:
536
+ attr.max_length = attr.parameters.max_length
537
+ elif attr.max_length is not None:
538
+ attr.parameters.max_length = attr.max_length
539
+
540
+ def _reconcile_text_attribute_parameters(self, schema: SchemaRoot | None = None) -> None:
541
+ """Reconcile regex, min_length, max_length between deprecated fields and parameters for Text attributes.
542
+
543
+ Args:
544
+ schema: If provided, reconcile incoming schema data before merging.
545
+ If None, reconcile already-loaded schemas (e.g., from database).
546
+ """
547
+ if schema:
548
+ # Incoming schema: modify in place
549
+ for item in schema.nodes + schema.generics:
550
+ for attr in item.attributes:
551
+ self._reconcile_text_attr(attr)
552
+ return
553
+
554
+ # Loaded schemas: need to duplicate before modifying
555
+ for name in self.all_names:
556
+ node = self.get(name=name, duplicate=False)
557
+
558
+ if not any(self._text_attr_needs_reconciliation(attr) for attr in node.attributes):
559
+ continue
560
+
561
+ node = node.duplicate()
562
+ for attr in node.attributes:
563
+ self._reconcile_text_attr(attr)
564
+ self.set(name=name, schema=node)
565
+
504
566
  def load_schema(self, schema: SchemaRoot) -> None:
505
567
  """Load a SchemaRoot object and store all NodeSchema or GenericSchema.
506
568
 
507
569
  In the current implementation, if a schema object present in the SchemaRoot already exist, it will be overwritten.
508
570
  """
571
+ # Reconcile deprecated text attribute parameters before merging
572
+ self._reconcile_text_attribute_parameters(schema)
573
+
509
574
  for item in schema.nodes + schema.generics:
510
575
  try:
511
576
  if item.id:
@@ -545,6 +610,7 @@ class SchemaBranch:
545
610
  self.generate_identifiers()
546
611
  self.process_default_values()
547
612
  self.process_deprecations()
613
+ self._reconcile_text_attribute_parameters()
548
614
  self.process_cardinality_counts()
549
615
  self.process_inheritance()
550
616
  self.process_hierarchy()
infrahub/core/utils.py CHANGED
@@ -215,7 +215,7 @@ def props(x: Any) -> dict[str, Any]:
215
215
 
216
216
 
217
217
  class SubclassWithMeta_Meta(type):
218
- _meta = None
218
+ _meta: Any = None
219
219
 
220
220
  def __str__(cls) -> str:
221
221
  if cls._meta:
@@ -229,7 +229,7 @@ class SubclassWithMeta_Meta(type):
229
229
  class SubclassWithMeta(metaclass=SubclassWithMeta_Meta):
230
230
  """This class improves __init_subclass__ to receive automatically the options from meta"""
231
231
 
232
- def __init_subclass__(cls, **meta_options: dict[str, Any]) -> None:
232
+ def __init_subclass__(cls, **meta_options: Any) -> None:
233
233
  """This method just terminates the super() chain"""
234
234
  _Meta = getattr(cls, "Meta", None)
235
235
  _meta_props = {}
@@ -254,5 +254,5 @@ class SubclassWithMeta(metaclass=SubclassWithMeta_Meta):
254
254
  super_class.__init_subclass_with_meta__(**options)
255
255
 
256
256
  @classmethod
257
- def __init_subclass_with_meta__(cls, **meta_options: dict[str, Any]) -> None:
257
+ def __init_subclass_with_meta__(cls, **meta_options: Any) -> None:
258
258
  """This method just terminates the super() chain"""
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
20
20
  class AggregatedConstraintChecker:
21
21
  def __init__(
22
22
  self, constraints: list[ConstraintCheckerInterface], db: InfrahubDatabase, branch: Branch | None = None
23
- ):
23
+ ) -> None:
24
24
  self.constraints = constraints
25
25
  self.db = db
26
26
  self.branch = branch
@@ -84,7 +84,7 @@ class AttributeChoicesUpdateValidatorQuery(AttributeSchemaValidatorQuery):
84
84
  class AttributeChoicesChecker(ConstraintCheckerInterface):
85
85
  query_classes = [AttributeChoicesUpdateValidatorQuery]
86
86
 
87
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
87
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
88
88
  self.db = db
89
89
  self.branch = branch
90
90
 
@@ -72,7 +72,7 @@ class AttributeEnumUpdateValidatorQuery(AttributeSchemaValidatorQuery):
72
72
  class AttributeEnumChecker(ConstraintCheckerInterface):
73
73
  query_classes = [AttributeEnumUpdateValidatorQuery]
74
74
 
75
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
75
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
76
76
  self.db = db
77
77
  self.branch = branch
78
78
 
@@ -36,7 +36,7 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
36
36
  self.params["null_value"] = NULL_VALUE
37
37
 
38
38
  query = """
39
- MATCH p = (n:%(node_kind)s)
39
+ MATCH (n:%(node_kinds)s)
40
40
  CALL (n) {
41
41
  MATCH path = (root:Root)<-[rr:IS_PART_OF]-(n)-[ra:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name } )-[rv:HAS_VALUE]-(av:AttributeValue)
42
42
  WHERE all(
@@ -51,7 +51,10 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
51
51
  WHERE all(r in relationships(full_path) WHERE r.status = "active")
52
52
  AND attribute_value IS NOT NULL
53
53
  AND attribute_value <> $null_value
54
- """ % {"branch_filter": branch_filter, "node_kind": self.node_schema.kind}
54
+ """ % {
55
+ "branch_filter": branch_filter,
56
+ "node_kinds": f"{self.node_schema.kind}|Profile{self.node_schema.kind}|Template{self.node_schema.kind}",
57
+ }
55
58
 
56
59
  self.add_to_query(query)
57
60
  self.return_labels = ["node.uuid", "attribute_value", "value_relationship.branch as value_branch"]
@@ -89,7 +92,7 @@ class AttributeKindUpdateValidatorQuery(AttributeSchemaValidatorQuery):
89
92
  class AttributeKindChecker(ConstraintCheckerInterface):
90
93
  query_classes = [AttributeKindUpdateValidatorQuery]
91
94
 
92
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
95
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
93
96
  self.db = db
94
97
  self.branch = branch
95
98
 
@@ -69,7 +69,7 @@ class AttributeLengthUpdateValidatorQuery(AttributeSchemaValidatorQuery):
69
69
  class AttributeLengthChecker(ConstraintCheckerInterface):
70
70
  query_classes = [AttributeLengthUpdateValidatorQuery]
71
71
 
72
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
72
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
73
73
  self.db = db
74
74
  self.branch = branch
75
75
 
@@ -79,7 +79,7 @@ class AttributeNumberUpdateValidatorQuery(AttributeSchemaValidatorQuery):
79
79
  class AttributeNumberChecker(ConstraintCheckerInterface):
80
80
  query_classes = [AttributeNumberUpdateValidatorQuery]
81
81
 
82
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
82
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
83
83
  self.db = db
84
84
  self.branch = branch
85
85
 
@@ -74,7 +74,7 @@ class AttributeNumberPoolUpdateValidatorQuery(AttributeSchemaValidatorQuery):
74
74
  class AttributeNumberPoolChecker(ConstraintCheckerInterface):
75
75
  query_classes = [AttributeNumberPoolUpdateValidatorQuery]
76
76
 
77
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
77
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
78
78
  self.db = db
79
79
  self.branch = branch
80
80
 
@@ -64,7 +64,7 @@ class AttributeOptionalUpdateValidatorQuery(AttributeSchemaValidatorQuery):
64
64
  class AttributeOptionalChecker(ConstraintCheckerInterface):
65
65
  query_classes = [AttributeOptionalUpdateValidatorQuery]
66
66
 
67
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
67
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
68
68
  self.db = db
69
69
  self.branch = branch
70
70
 
@@ -69,7 +69,7 @@ class AttributeRegexUpdateValidatorQuery(AttributeSchemaValidatorQuery):
69
69
  class AttributeRegexChecker(ConstraintCheckerInterface):
70
70
  query_classes = [AttributeRegexUpdateValidatorQuery]
71
71
 
72
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
72
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
73
73
  self.db = db
74
74
  self.branch = branch
75
75
 
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
19
19
  class NodeAttributeAddChecker(ConstraintCheckerInterface):
20
20
  query_classes = [NodeNotPresentValidatorQuery]
21
21
 
22
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
22
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
23
23
  self.db = db
24
24
  self.branch = branch
25
25
 
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
  class NodeRelationshipAddChecker(ConstraintCheckerInterface):
17
17
  query_classes = [NodeNotPresentValidatorQuery]
18
18
 
19
- def __init__(self, db: InfrahubDatabase, branch: Branch | None = None):
19
+ def __init__(self, db: InfrahubDatabase, branch: Branch | None = None) -> None:
20
20
  self.db = db
21
21
  self.branch = branch
22
22
 
@@ -136,7 +136,7 @@ class RelationshipPeerParentValidatorQuery(RelationshipSchemaValidatorQuery):
136
136
  parent_relationship: RelationshipSchema,
137
137
  peer_parent_relationship: RelationshipSchema,
138
138
  **kwargs: Any,
139
- ):
139
+ ) -> None:
140
140
  super().__init__(**kwargs)
141
141
 
142
142
  self.relationship = relationship
@@ -152,7 +152,7 @@ class InfrahubDatabase:
152
152
  session_mode: InfrahubDatabaseSessionMode = InfrahubDatabaseSessionMode.WRITE,
153
153
  transaction: AsyncTransaction | None = None,
154
154
  queries_names_to_config: dict[str, QueryConfig] | None = None,
155
- ):
155
+ ) -> None:
156
156
  self._mode: InfrahubDatabaseMode = mode
157
157
  self._driver: AsyncDriver = driver
158
158
  self._session: AsyncSession | None = session
infrahub/git/utils.py CHANGED
@@ -16,9 +16,9 @@ from infrahub_sdk.types import Order
16
16
  from infrahub.core import registry
17
17
  from infrahub.core.constants import InfrahubKind
18
18
  from infrahub.core.manager import NodeManager
19
+ from infrahub.core.order import OrderModel
19
20
  from infrahub.database import InfrahubDatabase
20
21
  from infrahub.generators.models import ProposedChangeGeneratorDefinition
21
- from infrahub.graphql.models import OrderModel
22
22
 
23
23
  from .. import config
24
24
  from .models import RepositoryBranchInfo, RepositoryData
infrahub/graphql/app.py CHANGED
@@ -172,9 +172,9 @@ class InfrahubGraphQLApp:
172
172
 
173
173
  response = handler(request)
174
174
  if isawaitable(response):
175
- return await cast("Awaitable[Response]", response)
175
+ return await response
176
176
 
177
- return cast("Response", response)
177
+ return response
178
178
 
179
179
  async def _handle_http_request(
180
180
  self, request: Request, db: InfrahubDatabase, branch: Branch, account_session: AccountSession
@@ -12,7 +12,7 @@ from graphql import (
12
12
  class GraphQLFieldExtractor:
13
13
  """Class to extract fields from a GraphQL selection set."""
14
14
 
15
- def __init__(self, info: GraphQLResolveInfo):
15
+ def __init__(self, info: GraphQLResolveInfo) -> None:
16
16
  self.info = info
17
17
  self.fragments = info.fragments
18
18