infrahub-server 1.6.2__py3-none-any.whl → 1.7.0__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 (253) hide show
  1. infrahub/actions/tasks.py +4 -2
  2. infrahub/api/exceptions.py +2 -2
  3. infrahub/api/schema.py +3 -1
  4. infrahub/artifacts/tasks.py +1 -0
  5. infrahub/auth.py +2 -2
  6. infrahub/cli/db.py +54 -28
  7. infrahub/computed_attribute/gather.py +3 -4
  8. infrahub/computed_attribute/tasks.py +23 -6
  9. infrahub/config.py +8 -0
  10. infrahub/constants/enums.py +12 -0
  11. infrahub/core/account.py +12 -9
  12. infrahub/core/attribute.py +106 -108
  13. infrahub/core/branch/models.py +44 -71
  14. infrahub/core/branch/tasks.py +5 -3
  15. infrahub/core/changelog/diff.py +1 -20
  16. infrahub/core/changelog/models.py +0 -7
  17. infrahub/core/constants/__init__.py +17 -0
  18. infrahub/core/constants/database.py +0 -1
  19. infrahub/core/constants/schema.py +0 -1
  20. infrahub/core/convert_object_type/repository_conversion.py +3 -4
  21. infrahub/core/diff/branch_differ.py +1 -1
  22. infrahub/core/diff/conflict_transferer.py +1 -1
  23. infrahub/core/diff/data_check_synchronizer.py +4 -3
  24. infrahub/core/diff/enricher/cardinality_one.py +2 -2
  25. infrahub/core/diff/enricher/hierarchy.py +1 -1
  26. infrahub/core/diff/enricher/labels.py +1 -1
  27. infrahub/core/diff/merger/merger.py +28 -2
  28. infrahub/core/diff/merger/serializer.py +3 -10
  29. infrahub/core/diff/model/diff.py +1 -1
  30. infrahub/core/diff/query/merge.py +376 -135
  31. infrahub/core/diff/repository/repository.py +3 -1
  32. infrahub/core/graph/__init__.py +1 -1
  33. infrahub/core/graph/constraints.py +3 -3
  34. infrahub/core/graph/schema.py +2 -12
  35. infrahub/core/ipam/reconciler.py +8 -6
  36. infrahub/core/ipam/utilization.py +8 -15
  37. infrahub/core/manager.py +133 -152
  38. infrahub/core/merge.py +1 -1
  39. infrahub/core/metadata/__init__.py +0 -0
  40. infrahub/core/metadata/interface.py +37 -0
  41. infrahub/core/metadata/model.py +31 -0
  42. infrahub/core/metadata/query/__init__.py +0 -0
  43. infrahub/core/metadata/query/node_metadata.py +301 -0
  44. infrahub/core/migrations/graph/__init__.py +4 -0
  45. infrahub/core/migrations/graph/m012_convert_account_generic.py +12 -12
  46. infrahub/core/migrations/graph/m013_convert_git_password_credential.py +7 -12
  47. infrahub/core/migrations/graph/m017_add_core_profile.py +5 -2
  48. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +2 -1
  49. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +0 -10
  50. infrahub/core/migrations/graph/m020_duplicate_edges.py +0 -8
  51. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +2 -1
  52. infrahub/core/migrations/graph/m026_0000_prefix_fix.py +2 -1
  53. infrahub/core/migrations/graph/m029_duplicates_cleanup.py +0 -1
  54. infrahub/core/migrations/graph/m031_check_number_attributes.py +2 -2
  55. infrahub/core/migrations/graph/m038_redo_0000_prefix_fix.py +2 -1
  56. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +1 -1
  57. infrahub/core/migrations/graph/m049_remove_is_visible_relationship.py +53 -0
  58. infrahub/core/migrations/graph/m050_backfill_vertex_metadata.py +168 -0
  59. infrahub/core/migrations/query/__init__.py +2 -2
  60. infrahub/core/migrations/query/attribute_add.py +17 -6
  61. infrahub/core/migrations/query/attribute_remove.py +19 -5
  62. infrahub/core/migrations/query/attribute_rename.py +21 -5
  63. infrahub/core/migrations/query/node_duplicate.py +19 -4
  64. infrahub/core/migrations/query/schema_attribute_update.py +1 -1
  65. infrahub/core/migrations/schema/attribute_kind_update.py +26 -6
  66. infrahub/core/migrations/schema/attribute_name_update.py +1 -1
  67. infrahub/core/migrations/schema/attribute_supports_profile.py +5 -3
  68. infrahub/core/migrations/schema/models.py +3 -0
  69. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  70. infrahub/core/migrations/schema/node_attribute_remove.py +1 -1
  71. infrahub/core/migrations/schema/node_kind_update.py +1 -1
  72. infrahub/core/migrations/schema/node_remove.py +24 -2
  73. infrahub/core/migrations/schema/tasks.py +4 -1
  74. infrahub/core/migrations/shared.py +13 -6
  75. infrahub/core/models.py +6 -6
  76. infrahub/core/node/__init__.py +157 -58
  77. infrahub/core/node/base.py +9 -5
  78. infrahub/core/node/create.py +7 -3
  79. infrahub/core/node/delete_validator.py +1 -1
  80. infrahub/core/node/standard.py +100 -14
  81. infrahub/core/order.py +30 -0
  82. infrahub/core/property.py +0 -1
  83. infrahub/core/protocols.py +1 -0
  84. infrahub/core/protocols_base.py +10 -2
  85. infrahub/core/query/__init__.py +11 -6
  86. infrahub/core/query/attribute.py +164 -49
  87. infrahub/core/query/branch.py +58 -70
  88. infrahub/core/query/delete.py +1 -1
  89. infrahub/core/query/diff.py +7 -7
  90. infrahub/core/query/ipam.py +104 -43
  91. infrahub/core/query/node.py +1072 -281
  92. infrahub/core/query/relationship.py +531 -325
  93. infrahub/core/query/resource_manager.py +107 -18
  94. infrahub/core/query/standard_node.py +25 -5
  95. infrahub/core/query/utils.py +2 -4
  96. infrahub/core/relationship/constraints/count.py +1 -1
  97. infrahub/core/relationship/constraints/peer_kind.py +1 -1
  98. infrahub/core/relationship/constraints/peer_parent.py +1 -1
  99. infrahub/core/relationship/constraints/peer_relatives.py +1 -1
  100. infrahub/core/relationship/constraints/profiles_kind.py +1 -1
  101. infrahub/core/relationship/constraints/profiles_removal.py +168 -0
  102. infrahub/core/relationship/model.py +293 -139
  103. infrahub/core/schema/attribute_parameters.py +28 -1
  104. infrahub/core/schema/attribute_schema.py +11 -17
  105. infrahub/core/schema/basenode_schema.py +3 -0
  106. infrahub/core/schema/definitions/core/__init__.py +8 -2
  107. infrahub/core/schema/definitions/core/account.py +10 -10
  108. infrahub/core/schema/definitions/core/artifact.py +14 -8
  109. infrahub/core/schema/definitions/core/check.py +10 -4
  110. infrahub/core/schema/definitions/core/generator.py +26 -6
  111. infrahub/core/schema/definitions/core/graphql_query.py +1 -1
  112. infrahub/core/schema/definitions/core/group.py +9 -2
  113. infrahub/core/schema/definitions/core/ipam.py +80 -10
  114. infrahub/core/schema/definitions/core/menu.py +41 -7
  115. infrahub/core/schema/definitions/core/permission.py +16 -2
  116. infrahub/core/schema/definitions/core/profile.py +16 -2
  117. infrahub/core/schema/definitions/core/propose_change.py +24 -4
  118. infrahub/core/schema/definitions/core/propose_change_comment.py +23 -11
  119. infrahub/core/schema/definitions/core/propose_change_validator.py +50 -21
  120. infrahub/core/schema/definitions/core/repository.py +10 -0
  121. infrahub/core/schema/definitions/core/resource_pool.py +8 -1
  122. infrahub/core/schema/definitions/core/template.py +19 -2
  123. infrahub/core/schema/definitions/core/transform.py +11 -5
  124. infrahub/core/schema/definitions/core/webhook.py +27 -9
  125. infrahub/core/schema/manager.py +63 -43
  126. infrahub/core/schema/relationship_schema.py +6 -2
  127. infrahub/core/schema/schema_branch.py +115 -11
  128. infrahub/core/task/task.py +4 -2
  129. infrahub/core/utils.py +3 -25
  130. infrahub/core/validators/aggregated_checker.py +1 -1
  131. infrahub/core/validators/attribute/choices.py +1 -1
  132. infrahub/core/validators/attribute/enum.py +1 -1
  133. infrahub/core/validators/attribute/kind.py +6 -3
  134. infrahub/core/validators/attribute/length.py +1 -1
  135. infrahub/core/validators/attribute/min_max.py +1 -1
  136. infrahub/core/validators/attribute/number_pool.py +1 -1
  137. infrahub/core/validators/attribute/optional.py +1 -1
  138. infrahub/core/validators/attribute/regex.py +1 -1
  139. infrahub/core/validators/determiner.py +3 -3
  140. infrahub/core/validators/node/attribute.py +1 -1
  141. infrahub/core/validators/node/relationship.py +1 -1
  142. infrahub/core/validators/relationship/peer.py +1 -1
  143. infrahub/database/__init__.py +4 -4
  144. infrahub/dependencies/builder/constraint/grouped/node_runner.py +2 -0
  145. infrahub/dependencies/builder/constraint/relationship_manager/profiles_removal.py +8 -0
  146. infrahub/dependencies/registry.py +2 -0
  147. infrahub/display_labels/tasks.py +12 -3
  148. infrahub/git/integrator.py +18 -18
  149. infrahub/git/tasks.py +1 -1
  150. infrahub/git/utils.py +1 -1
  151. infrahub/graphql/app.py +2 -2
  152. infrahub/graphql/constants.py +3 -0
  153. infrahub/graphql/context.py +1 -1
  154. infrahub/graphql/field_extractor.py +1 -1
  155. infrahub/graphql/initialization.py +11 -0
  156. infrahub/graphql/loaders/account.py +134 -0
  157. infrahub/graphql/loaders/node.py +5 -12
  158. infrahub/graphql/loaders/peers.py +5 -7
  159. infrahub/graphql/manager.py +175 -21
  160. infrahub/graphql/metadata.py +91 -0
  161. infrahub/graphql/mutations/account.py +6 -6
  162. infrahub/graphql/mutations/attribute.py +0 -2
  163. infrahub/graphql/mutations/branch.py +9 -5
  164. infrahub/graphql/mutations/computed_attribute.py +1 -1
  165. infrahub/graphql/mutations/display_label.py +1 -1
  166. infrahub/graphql/mutations/hfid.py +1 -1
  167. infrahub/graphql/mutations/ipam.py +4 -6
  168. infrahub/graphql/mutations/main.py +9 -4
  169. infrahub/graphql/mutations/profile.py +16 -22
  170. infrahub/graphql/mutations/proposed_change.py +4 -4
  171. infrahub/graphql/mutations/relationship.py +40 -10
  172. infrahub/graphql/mutations/repository.py +14 -12
  173. infrahub/graphql/mutations/schema.py +2 -2
  174. infrahub/graphql/order.py +14 -0
  175. infrahub/graphql/queries/branch.py +62 -6
  176. infrahub/graphql/queries/diff/tree.py +5 -5
  177. infrahub/graphql/queries/resource_manager.py +25 -24
  178. infrahub/graphql/resolvers/account_metadata.py +84 -0
  179. infrahub/graphql/resolvers/ipam.py +6 -8
  180. infrahub/graphql/resolvers/many_relationship.py +77 -35
  181. infrahub/graphql/resolvers/resolver.py +59 -14
  182. infrahub/graphql/resolvers/single_relationship.py +87 -23
  183. infrahub/graphql/subscription/graphql_query.py +2 -0
  184. infrahub/graphql/types/__init__.py +0 -1
  185. infrahub/graphql/types/attribute.py +10 -5
  186. infrahub/graphql/types/branch.py +40 -53
  187. infrahub/graphql/types/enums.py +3 -0
  188. infrahub/graphql/types/metadata.py +28 -0
  189. infrahub/graphql/types/node.py +22 -2
  190. infrahub/graphql/types/relationship.py +10 -2
  191. infrahub/graphql/types/standard_node.py +12 -7
  192. infrahub/hfid/tasks.py +12 -3
  193. infrahub/lock.py +7 -0
  194. infrahub/menu/repository.py +1 -1
  195. infrahub/patch/queries/base.py +1 -1
  196. infrahub/pools/number.py +1 -8
  197. infrahub/profiles/gather.py +56 -0
  198. infrahub/profiles/mandatory_fields_checker.py +116 -0
  199. infrahub/profiles/models.py +66 -0
  200. infrahub/profiles/node_applier.py +154 -13
  201. infrahub/profiles/queries/get_profile_data.py +143 -31
  202. infrahub/profiles/tasks.py +79 -27
  203. infrahub/profiles/triggers.py +22 -0
  204. infrahub/proposed_change/action_checker.py +1 -1
  205. infrahub/proposed_change/tasks.py +4 -1
  206. infrahub/services/__init__.py +1 -1
  207. infrahub/services/adapters/cache/nats.py +1 -1
  208. infrahub/services/adapters/cache/redis.py +7 -0
  209. infrahub/tasks/artifact.py +1 -0
  210. infrahub/transformations/tasks.py +2 -2
  211. infrahub/trigger/catalogue.py +2 -0
  212. infrahub/trigger/models.py +1 -0
  213. infrahub/trigger/setup.py +3 -3
  214. infrahub/trigger/tasks.py +3 -0
  215. infrahub/validators/tasks.py +1 -0
  216. infrahub/webhook/gather.py +1 -1
  217. infrahub/webhook/models.py +1 -1
  218. infrahub/webhook/tasks.py +23 -7
  219. infrahub/workers/dependencies.py +9 -3
  220. infrahub/workers/infrahub_async.py +13 -4
  221. infrahub/workflows/catalogue.py +19 -0
  222. infrahub_sdk/analyzer.py +2 -2
  223. infrahub_sdk/branch.py +12 -39
  224. infrahub_sdk/checks.py +4 -4
  225. infrahub_sdk/client.py +36 -0
  226. infrahub_sdk/ctl/cli_commands.py +2 -1
  227. infrahub_sdk/ctl/graphql.py +15 -4
  228. infrahub_sdk/ctl/utils.py +2 -2
  229. infrahub_sdk/enums.py +6 -0
  230. infrahub_sdk/graphql/renderers.py +21 -0
  231. infrahub_sdk/graphql/utils.py +85 -0
  232. infrahub_sdk/node/attribute.py +12 -2
  233. infrahub_sdk/node/constants.py +12 -0
  234. infrahub_sdk/node/metadata.py +69 -0
  235. infrahub_sdk/node/node.py +65 -14
  236. infrahub_sdk/node/property.py +3 -0
  237. infrahub_sdk/node/related_node.py +37 -5
  238. infrahub_sdk/node/relationship.py +18 -1
  239. infrahub_sdk/operation.py +2 -2
  240. infrahub_sdk/schema/repository.py +1 -2
  241. infrahub_sdk/transforms.py +2 -2
  242. infrahub_sdk/types.py +18 -2
  243. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/METADATA +17 -16
  244. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/RECORD +252 -231
  245. infrahub_testcontainers/container.py +3 -3
  246. infrahub_testcontainers/docker-compose-cluster.test.yml +7 -7
  247. infrahub_testcontainers/docker-compose.test.yml +13 -5
  248. infrahub_testcontainers/models.py +3 -3
  249. infrahub_testcontainers/performance_test.py +1 -1
  250. infrahub/graphql/models.py +0 -6
  251. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/WHEEL +0 -0
  252. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/entry_points.txt +0 -0
  253. {infrahub_server-1.6.2.dist-info → infrahub_server-1.7.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,15 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import inspect
4
+ from dataclasses import dataclass, field
4
5
  from enum import Enum
5
6
  from typing import TYPE_CHECKING, Any, Optional, Union, get_args, get_origin
6
7
  from uuid import UUID
7
8
 
8
9
  import ujson
9
10
  from infrahub_sdk.uuidt import UUIDT
10
- from pydantic import BaseModel
11
+ from pydantic import BaseModel, Field, field_validator
11
12
 
12
- from infrahub.core.constants import NULL_VALUE
13
+ from infrahub.constants.enums import OrderByField, OrderDirection
14
+ from infrahub.core.constants import NULL_VALUE, SYSTEM_USER_ID, InfrahubKind
13
15
  from infrahub.core.query.standard_node import (
14
16
  StandardNodeCreateQuery,
15
17
  StandardNodeDeleteQuery,
@@ -18,6 +20,7 @@ from infrahub.core.query.standard_node import (
18
20
  StandardNodeQuery,
19
21
  StandardNodeUpdateQuery,
20
22
  )
23
+ from infrahub.core.timestamp import Timestamp, current_timestamp
21
24
  from infrahub.exceptions import Error, InitializationError
22
25
 
23
26
  if TYPE_CHECKING:
@@ -29,9 +32,25 @@ if TYPE_CHECKING:
29
32
  from infrahub.database import InfrahubDatabase
30
33
 
31
34
 
35
+ @dataclass
36
+ class StandardNodeOrdering:
37
+ order_by: OrderByField = field(default=OrderByField.ID)
38
+ direction: OrderDirection = field(default=OrderDirection.ASC)
39
+
40
+
41
+ @dataclass(slots=True)
42
+ class StandardNodeQueryFields:
43
+ node: dict[str, Any] = field(default_factory=dict)
44
+ node_metadata: dict[str, Any] = field(default_factory=dict)
45
+
46
+
32
47
  class StandardNode(BaseModel):
33
48
  id: Optional[str] = None
34
49
  uuid: Optional[UUID] = None
50
+ created_at: Optional[str] = Field(default=None, validate_default=True)
51
+ created_by: str = Field(default=SYSTEM_USER_ID)
52
+ updated_by: Optional[str] = Field(default=None)
53
+ updated_at: Optional[str] = Field(default=None, validate_default=True)
35
54
 
36
55
  _query: type[StandardNodeQuery] = StandardNodeCreateQuery
37
56
  _exclude_attrs: list[str] = ["id", "uuid", "_query"]
@@ -45,6 +64,11 @@ class StandardNode(BaseModel):
45
64
  raise ValueError("id isn't defined yet")
46
65
  return self.id
47
66
 
67
+ @field_validator("created_at", mode="before")
68
+ @classmethod
69
+ def set_created_at(cls, value: str) -> str:
70
+ return Timestamp(value).to_string()
71
+
48
72
  @staticmethod
49
73
  def guess_field_type(field: FieldInfo) -> Any:
50
74
  """Return the type of a Pydantic model field.
@@ -68,7 +92,14 @@ class StandardNode(BaseModel):
68
92
 
69
93
  raise InitializationError("The root node has not been initialized with a uuid")
70
94
 
71
- async def to_graphql(self, fields: dict) -> dict:
95
+ async def to_graphql_flat(self, fields: dict) -> dict:
96
+ """Returns the GraphQL representation of the object with only top-level fields.
97
+
98
+ This method does not handle nested fields and is only used for the old `Branch` query which
99
+ will be deprecated in the future and replaced by the `InfrahubBranch` query.
100
+ It's also used for the old style of Branch mutations that will be deprecated in the future,
101
+ when we introduce InfrahubBranch muations for consistency.
102
+ """
72
103
  response: dict[str, Any] = {"id": self.uuid}
73
104
 
74
105
  for field_name in fields.keys():
@@ -77,21 +108,71 @@ class StandardNode(BaseModel):
77
108
  if field_name == "__typename":
78
109
  response[field_name] = self.get_type()
79
110
  continue
80
- field = getattr(self, field_name)
111
+ field = getattr(self, field_name, None)
81
112
  if field is None:
82
113
  response[field_name] = None
83
114
  continue
115
+ if isinstance(fields.get(field_name), dict):
116
+ result = {}
117
+ for nested_field in fields.get(field_name, {}).keys():
118
+ if nested_field == "value":
119
+ result[nested_field] = field
120
+ continue
121
+ response[field_name] = result
122
+ continue
123
+
84
124
  response[field_name] = field
85
125
 
86
126
  return response
87
127
 
88
- async def save(self, db: InfrahubDatabase) -> bool:
128
+ async def to_graphql(self, fields: StandardNodeQueryFields) -> dict:
129
+ node_response: dict[str, Any] = {}
130
+ meta_response: dict[str, Any] = {}
131
+
132
+ for field_name in fields.node.keys():
133
+ if field_name == "id":
134
+ node_response["id"] = self.uuid
135
+ continue
136
+ if field_name == "__typename":
137
+ node_response[field_name] = self.get_type()
138
+ continue
139
+ field = getattr(self, field_name, None)
140
+ if field is None:
141
+ node_response[field_name] = None
142
+ continue
143
+ if isinstance(fields.node.get(field_name), dict):
144
+ result = {}
145
+ for nested_field in fields.node.get(field_name, {}).keys():
146
+ if nested_field == "value":
147
+ result[nested_field] = field
148
+ continue
149
+ node_response[field_name] = result
150
+ continue
151
+
152
+ node_response[field_name] = field
153
+
154
+ for field_name in fields.node_metadata.keys():
155
+ match field_name:
156
+ case "created_at":
157
+ meta_response["created_at"] = Timestamp(self.created_at).to_datetime() if self.created_at else None
158
+ case "updated_at":
159
+ meta_response["updated_at"] = Timestamp(self.updated_at).to_datetime() if self.updated_at else None
160
+ case "created_by":
161
+ if self.created_by and self.created_by != SYSTEM_USER_ID:
162
+ meta_response["created_by"] = {"id": self.created_by, "__kind__": InfrahubKind.ACCOUNT}
163
+ case "updated_by":
164
+ if self.updated_by and self.updated_by != SYSTEM_USER_ID:
165
+ meta_response["updated_by"] = {"id": self.updated_by, "__kind__": InfrahubKind.ACCOUNT}
166
+
167
+ return {"node": node_response, "node_metadata": meta_response}
168
+
169
+ async def save(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
89
170
  """Create or Update the Node in the database."""
90
171
 
91
172
  if self.id:
92
- return await self.update(db=db)
173
+ return await self.update(db=db, user_id=user_id)
93
174
 
94
- return await self.create(db=db)
175
+ return await self.create(db=db, user_id=user_id)
95
176
 
96
177
  async def delete(self, db: InfrahubDatabase) -> None:
97
178
  """Delete the Node in the database."""
@@ -99,9 +180,11 @@ class StandardNode(BaseModel):
99
180
  query: Query = await StandardNodeDeleteQuery.init(db=db, node=self)
100
181
  await query.execute(db=db)
101
182
 
102
- async def create(self, db: InfrahubDatabase) -> bool:
183
+ async def create(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
103
184
  """Create a new node in the database."""
104
-
185
+ self.created_by = user_id
186
+ self.updated_by = self.created_by
187
+ self.updated_at = self.created_at
105
188
  query: Query = await self._query.init(db=db, node=self)
106
189
  await query.execute(db=db)
107
190
 
@@ -115,9 +198,10 @@ class StandardNode(BaseModel):
115
198
 
116
199
  return True
117
200
 
118
- async def update(self, db: InfrahubDatabase) -> bool:
201
+ async def update(self, db: InfrahubDatabase, user_id: str = SYSTEM_USER_ID) -> bool:
119
202
  """Update the node in the database if needed."""
120
-
203
+ self.updated_by = user_id
204
+ self.updated_at = current_timestamp()
121
205
  query: Query = await StandardNodeUpdateQuery.init(db=db, node=self)
122
206
  await query.execute(db=db)
123
207
  result = query.get_result()
@@ -187,7 +271,7 @@ class StandardNode(BaseModel):
187
271
  else:
188
272
  data["uuid"] = str(self.uuid)
189
273
 
190
- for attr_name, field in self.model_fields.items():
274
+ for attr_name, field_info in self.__class__.model_fields.items():
191
275
  if attr_name in self._exclude_attrs:
192
276
  continue
193
277
 
@@ -195,7 +279,7 @@ class StandardNode(BaseModel):
195
279
  if isinstance(attr_value, Enum):
196
280
  attr_value = attr_value.value
197
281
 
198
- field_type = self.guess_field_type(field)
282
+ field_type = self.guess_field_type(field_info)
199
283
 
200
284
  if attr_value is None:
201
285
  data[attr_name] = NULL_VALUE
@@ -219,10 +303,12 @@ class StandardNode(BaseModel):
219
303
  limit: int = 1000,
220
304
  ids: list[str] | None = None,
221
305
  name: str | None = None,
306
+ node_ordering: StandardNodeOrdering | None = None,
222
307
  **kwargs: dict[str, Any],
223
308
  ) -> list[Self]:
309
+ node_ordering = node_ordering or StandardNodeOrdering()
224
310
  query: Query = await StandardNodeGetListQuery.init(
225
- db=db, node_class=cls, ids=ids, node_name=name, limit=limit, **kwargs
311
+ db=db, node_class=cls, ids=ids, node_name=name, limit=limit, node_ordering=node_ordering, **kwargs
226
312
  )
227
313
  await query.execute(db=db)
228
314
 
infrahub/core/order.py ADDED
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Self
4
+
5
+ from pydantic import BaseModel, model_validator
6
+
7
+ from infrahub.constants.enums import OrderDirection # noqa: TC001
8
+ from infrahub.exceptions import ValidationError
9
+
10
+ # Metadata field name constants
11
+ METADATA_CREATED_AT = "created_at"
12
+ METADATA_CREATED_BY = "created_by"
13
+ METADATA_UPDATED_AT = "updated_at"
14
+ METADATA_UPDATED_BY = "updated_by"
15
+
16
+
17
+ class NodeMetaOrder(BaseModel):
18
+ created_at: OrderDirection | None = None
19
+ updated_at: OrderDirection | None = None
20
+
21
+
22
+ class OrderModel(BaseModel):
23
+ disable: bool | None = None
24
+ node_metadata: NodeMetaOrder | None = None
25
+
26
+ @model_validator(mode="after")
27
+ def validate_metadata(self) -> Self:
28
+ if self.node_metadata and self.node_metadata.created_at and self.node_metadata.updated_at:
29
+ raise ValidationError("Cannot order by both created_at and updated_at simultaneously.")
30
+ return self
infrahub/core/property.py CHANGED
@@ -34,7 +34,6 @@ class ClearValue(Enum):
34
34
  class FlagPropertyMixin:
35
35
  _flag_properties: list[str] = [v.value for v in FlagProperty]
36
36
 
37
- is_visible = True
38
37
  is_protected = False
39
38
 
40
39
  def _init_flag_property_mixin(self, kwargs: dict | None = None) -> None:
@@ -221,6 +221,7 @@ class CoreValidator(CoreNode):
221
221
  class CoreWebhook(CoreNode):
222
222
  name: String
223
223
  event_type: Enum
224
+ active: Boolean
224
225
  branch_scope: Dropdown
225
226
  node_kind: StringOptional
226
227
  description: StringOptional
@@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
4
4
 
5
5
  from typing_extensions import Self
6
6
 
7
+ from infrahub.core.constants import SYSTEM_USER_ID
8
+
7
9
  if TYPE_CHECKING:
8
10
  from neo4j import AsyncResult, AsyncSession, AsyncTransaction, Record
9
11
 
@@ -82,8 +84,10 @@ class CoreNode(Protocol):
82
84
  at: Timestamp | str | None = None,
83
85
  ) -> Self: ...
84
86
  async def new(self, db: InfrahubDatabase, id: str | None = None, **kwargs: Any) -> Self: ...
85
- async def save(self, db: InfrahubDatabase, at: Timestamp | None = None) -> Self: ...
86
- async def delete(self, db: InfrahubDatabase, at: Timestamp | None = None) -> None: ...
87
+ async def save(self, db: InfrahubDatabase, at: Timestamp | None = None, user_id: str = SYSTEM_USER_ID) -> Self: ...
88
+ async def delete(
89
+ self, db: InfrahubDatabase, at: Timestamp | None = None, user_id: str = SYSTEM_USER_ID
90
+ ) -> None: ...
87
91
  async def load(
88
92
  self,
89
93
  db: InfrahubDatabase,
@@ -102,3 +106,7 @@ class CoreNode(Protocol):
102
106
  ) -> dict: ...
103
107
  async def render_display_label(self, db: InfrahubDatabase | None = None) -> str: ...
104
108
  async def from_graphql(self, data: dict, db: InfrahubDatabase) -> bool: ...
109
+ def _get_created_at(self) -> Timestamp | None: ...
110
+ def _get_created_by(self) -> str | None: ...
111
+ def _get_updated_at(self) -> Timestamp | None: ...
112
+ def _get_updated_by(self) -> str | None: ...
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from abc import ABC, abstractmethod
4
3
  from collections import defaultdict
5
4
  from dataclasses import dataclass, field
6
5
  from enum import Enum
@@ -13,7 +12,7 @@ from neo4j.graph import Relationship as Neo4jRelationship
13
12
  from opentelemetry import trace
14
13
 
15
14
  from infrahub import config
16
- from infrahub.core.constants import PermissionLevel
15
+ from infrahub.core.constants import SYSTEM_USER_ID, PermissionLevel
17
16
  from infrahub.core.timestamp import Timestamp
18
17
  from infrahub.exceptions import QueryError
19
18
 
@@ -160,7 +159,7 @@ def cleanup_return_labels(labels: list[str]) -> list[str]:
160
159
 
161
160
 
162
161
  class QueryResult:
163
- def __init__(self, data: list[Neo4jNode | Neo4jRelationship | list[Neo4jNode]], labels: list[str]):
162
+ def __init__(self, data: list[Neo4jNode | Neo4jRelationship | list[Neo4jNode]], labels: list[str]) -> None:
164
163
  self.data = data
165
164
  self.labels = labels
166
165
  self.branch_score: int = 0
@@ -336,7 +335,7 @@ class QueryStat:
336
335
  return cls(**data)
337
336
 
338
337
 
339
- class Query(ABC):
338
+ class Query:
340
339
  name: str = "base-query"
341
340
  type: QueryType
342
341
 
@@ -352,7 +351,8 @@ class Query(ABC):
352
351
  offset: int | None = None,
353
352
  order_by: list[str] | None = None,
354
353
  branch_agnostic: bool = False,
355
- ):
354
+ user_id: str = SYSTEM_USER_ID,
355
+ ) -> None:
356
356
  if branch:
357
357
  self.branch = branch
358
358
 
@@ -367,6 +367,7 @@ class Query(ABC):
367
367
  self.limit = limit
368
368
  self.offset = offset
369
369
  self.order_by = order_by
370
+ self.user_id = user_id
370
371
 
371
372
  # Initialize internal variables
372
373
  self.params: dict = {}
@@ -403,8 +404,12 @@ class Query(ABC):
403
404
 
404
405
  return query
405
406
 
406
- @abstractmethod
407
407
  async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None:
408
+ # Avoid using this method for new queries and look at migrating older queries. The
409
+ # problem here is that we loose so much information with the `**kwargs` we should instead
410
+ # populate this information via the constructor and anything done within the existing query_init methods
411
+ # could either be handled within __init__ or via dedicated methods within each Query class where appropriate,
412
+ # i.e. things might need to happend in a certain order or we just want to separate the logic better.
408
413
  raise NotImplementedError
409
414
 
410
415
  def get_context(self) -> dict[str, str]: