infrahub-server 1.1.8__py3-none-any.whl → 1.1.9__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.
infrahub/config.py CHANGED
@@ -249,6 +249,12 @@ class DatabaseSettings(BaseSettings):
249
249
  retry_limit: int = Field(
250
250
  default=3, description="Maximum number of times a transient issue in a transaction should be retried."
251
251
  )
252
+ max_concurrent_queries: int = Field(
253
+ default=0, ge=0, description="Maximum number of concurrent queries that can run (0 means unlimited)."
254
+ )
255
+ max_concurrent_queries_delay: float = Field(
256
+ default=0.01, ge=0, description="Delay to add when max_concurrent_queries is reached."
257
+ )
252
258
 
253
259
  @property
254
260
  def database_name(self) -> str:
@@ -36,6 +36,7 @@ class DiffMergeQuery(Query):
36
36
  "target_branch": self.target_branch.name,
37
37
  "source_branch": self.source_branch_name,
38
38
  }
39
+ # ruff: noqa: E501
39
40
  query = """
40
41
  UNWIND $node_diff_dicts AS node_diff_map
41
42
  CALL {
@@ -242,9 +243,11 @@ CALL {
242
243
  CASE
243
244
  WHEN startNode(source_r_rel_2).uuid = r.uuid THEN "r"
244
245
  ELSE "l"
245
- END AS r2_dir
246
+ END AS r2_dir,
247
+ source_r_rel_1.hierarchy AS r1_hierarchy,
248
+ source_r_rel_2.hierarchy AS r2_hierarchy
246
249
  }
247
- WITH n, r, r1_dir, r2_dir, rel_name, rel_peer_id, related_rel_status
250
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
248
251
  CALL {
249
252
  WITH n, rel_name, rel_peer_id, related_rel_status
250
253
  OPTIONAL MATCH (n)
@@ -258,12 +261,12 @@ CALL {
258
261
  SET target_r_rel_1.to = $at
259
262
  SET target_r_rel_2.to = $at
260
263
  }
261
- WITH n, r, r1_dir, r2_dir, rel_name, rel_peer_id, related_rel_status
264
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
262
265
  // ------------------------------
263
266
  // conditionally create new IS_RELATED relationships on target_branch, if necessary
264
267
  // ------------------------------
265
268
  CALL {
266
- WITH n, r, r1_dir, r2_dir, rel_name, rel_peer_id, related_rel_status
269
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, rel_name, rel_peer_id, related_rel_status
267
270
  MATCH (p:Node {uuid: rel_peer_id})
268
271
  OPTIONAL MATCH (n)
269
272
  -[r_rel_1:IS_RELATED {branch: $target_branch, status: related_rel_status}]
@@ -274,42 +277,42 @@ CALL {
274
277
  AND (r_rel_1.to >= $at OR r_rel_1.to IS NULL)
275
278
  AND r_rel_2.from <= $at
276
279
  AND (r_rel_2.to >= $at OR r_rel_2.to IS NULL)
277
- WITH n, r, r1_dir, r2_dir, p, related_rel_status, r_rel_1, r_rel_2
280
+ WITH n, r, r1_dir, r2_dir, r1_hierarchy, r2_hierarchy, p, related_rel_status, r_rel_1, r_rel_2
278
281
  WHERE r_rel_1 IS NULL
279
282
  AND r_rel_2 IS NULL
280
283
  // ------------------------------
281
284
  // create IS_RELATED relationships with directions maintained from source
282
285
  // ------------------------------
283
286
  CALL {
284
- WITH n, r, r1_dir, related_rel_status
285
- WITH n, r, r1_dir, related_rel_status
287
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
288
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
286
289
  WHERE r1_dir = "r"
287
290
  CREATE (n)
288
- -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
291
+ -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy}]
289
292
  ->(r)
290
293
  }
291
294
  CALL {
292
- WITH n, r, r1_dir, related_rel_status
293
- WITH n, r, r1_dir, related_rel_status
295
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
296
+ WITH n, r, r1_dir, r1_hierarchy, related_rel_status
294
297
  WHERE r1_dir = "l"
295
298
  CREATE (n)
296
- <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
299
+ <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r1_hierarchy}]
297
300
  -(r)
298
301
  }
299
302
  CALL {
300
- WITH r, p, r2_dir, related_rel_status
301
- WITH r, p, r2_dir, related_rel_status
303
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
304
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
302
305
  WHERE r2_dir = "r"
303
306
  CREATE (r)
304
- -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
307
+ -[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy}]
305
308
  ->(p)
306
309
  }
307
310
  CALL {
308
- WITH r, p, r2_dir, related_rel_status
309
- WITH r, p, r2_dir, related_rel_status
311
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
312
+ WITH r, p, r2_dir, r2_hierarchy, related_rel_status
310
313
  WHERE r2_dir = "l"
311
314
  CREATE (r)
312
- <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status}]
315
+ <-[:IS_RELATED {branch: $target_branch, branch_level: $branch_level, from: $at, status: related_rel_status, hierarchy: r2_hierarchy}]
313
316
  -(p)
314
317
  }
315
318
  }
@@ -1 +1 @@
1
- GRAPH_VERSION = 20
1
+ GRAPH_VERSION = 21
@@ -22,6 +22,7 @@ from .m017_add_core_profile import Migration017
22
22
  from .m018_uniqueness_nulls import Migration018
23
23
  from .m019_restore_rels_to_time import Migration019
24
24
  from .m020_duplicate_edges import Migration020
25
+ from .m021_missing_hierarchy_merge import Migration021
25
26
 
26
27
  if TYPE_CHECKING:
27
28
  from infrahub.core.root import Root
@@ -49,6 +50,7 @@ MIGRATIONS: list[type[Union[GraphMigration, InternalSchemaMigration, ArbitraryMi
49
50
  Migration018,
50
51
  Migration019,
51
52
  Migration020,
53
+ Migration021,
52
54
  ]
53
55
 
54
56
 
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Sequence
4
+
5
+ from infrahub.core.migrations.shared import GraphMigration, MigrationResult
6
+ from infrahub.log import get_logger
7
+
8
+ from ...query import Query, QueryType
9
+
10
+ if TYPE_CHECKING:
11
+ from infrahub.database import InfrahubDatabase
12
+
13
+ log = get_logger()
14
+
15
+
16
+ class SetMissingHierarchyQuery(Query):
17
+ name = "set_missing_hierarchy"
18
+ type = QueryType.WRITE
19
+ insert_return = False
20
+
21
+ async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None:
22
+ query = """
23
+ MATCH (r:Root)
24
+ WITH r.default_branch AS default_branch
25
+ MATCH (n:Node)-[main_e:IS_RELATED {branch: default_branch}]-(rel:Relationship)
26
+ WHERE main_e.hierarchy IS NULL
27
+ CALL {
28
+ WITH n, main_e, rel
29
+ MATCH (n)-[branch_e:IS_RELATED]-(rel)
30
+ WHERE branch_e.hierarchy IS NOT NULL
31
+ AND branch_e.branch <> main_e.branch
32
+ AND branch_e.from < main_e.from
33
+ SET main_e.hierarchy = branch_e.hierarchy
34
+ }
35
+ """
36
+ self.add_to_query(query)
37
+
38
+
39
+ class Migration021(GraphMigration):
40
+ """
41
+ A bug in diff merge logic caused the hierarchy information on IS_RELATED edges to be lost when merged into
42
+ main. This migration sets the missing hierarchy data.
43
+ """
44
+
45
+ name: str = "021_replace_hierarchy"
46
+ minimum_version: int = 20
47
+ queries: Sequence[type[Query]] = [SetMissingHierarchyQuery]
48
+
49
+ async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult:
50
+ result = MigrationResult()
51
+ return result
@@ -666,18 +666,21 @@ class NodeListGetRelationshipsQuery(Query):
666
666
  MATCH paths_in = ((n)<-[r1:IS_RELATED]-(rel:Relationship)<-[r2:IS_RELATED]-(peer))
667
667
  WHERE ($relationship_identifiers IS NULL OR rel.name in $relationship_identifiers)
668
668
  AND all(r IN relationships(paths_in) WHERE (%(filters)s))
669
+ AND n.uuid <> peer.uuid
669
670
  RETURN n, rel, peer, r1, r2, "inbound" as direction
670
671
  UNION
671
672
  MATCH (n:Node) WHERE n.uuid IN $ids
672
673
  MATCH paths_out = ((n)-[r1:IS_RELATED]->(rel:Relationship)-[r2:IS_RELATED]->(peer))
673
674
  WHERE ($relationship_identifiers IS NULL OR rel.name in $relationship_identifiers)
674
675
  AND all(r IN relationships(paths_out) WHERE (%(filters)s))
676
+ AND n.uuid <> peer.uuid
675
677
  RETURN n, rel, peer, r1, r2, "outbound" as direction
676
678
  UNION
677
679
  MATCH (n:Node) WHERE n.uuid IN $ids
678
680
  MATCH paths_bidir = ((n)-[r1:IS_RELATED]->(rel:Relationship)<-[r2:IS_RELATED]-(peer))
679
681
  WHERE ($relationship_identifiers IS NULL OR rel.name in $relationship_identifiers)
680
682
  AND all(r IN relationships(paths_bidir) WHERE (%(filters)s))
683
+ AND n.uuid <> peer.uuid
681
684
  RETURN n, rel, peer, r1, r2, "bidirectional" as direction
682
685
  """ % {"filters": rels_filter}
683
686
 
@@ -34,7 +34,7 @@ from infrahub.utils import InfrahubStringEnum
34
34
 
35
35
  from .constants import DatabaseType, Neo4jRuntime
36
36
  from .memgraph import DatabaseManagerMemgraph
37
- from .metrics import QUERY_EXECUTION_METRICS, TRANSACTION_RETRIES
37
+ from .metrics import CONNECTION_POOL_USAGE, QUERY_EXECUTION_METRICS, TRANSACTION_RETRIES
38
38
  from .neo4j import DatabaseManagerNeo4j
39
39
 
40
40
  if TYPE_CHECKING:
@@ -335,6 +335,14 @@ class InfrahubDatabase:
335
335
  context: dict[str, str] | None = None,
336
336
  type: QueryType | None = None, # pylint: disable=redefined-builtin
337
337
  ) -> tuple[list[Record], dict[str, Any]]:
338
+ connpool_usage = self._driver._pool.in_use_connection_count(self._driver._pool.address)
339
+ CONNECTION_POOL_USAGE.labels(self._driver._pool.address).set(float(connpool_usage))
340
+
341
+ if config.SETTINGS.database.max_concurrent_queries:
342
+ while connpool_usage > config.SETTINGS.database.max_concurrent_queries: # noqa: ASYNC110
343
+ await asyncio.sleep(config.SETTINGS.database.max_concurrent_queries_delay)
344
+ connpool_usage = self._driver._pool.in_use_connection_count(self._driver._pool.address)
345
+
338
346
  with trace.get_tracer(__name__).start_as_current_span("execute_db_query_with_metadata") as span:
339
347
  span.set_attribute("query", query)
340
348
  if name:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from prometheus_client import Counter, Histogram
3
+ from prometheus_client import Counter, Gauge, Histogram
4
4
 
5
5
  METRIC_PREFIX = "infrahub_db"
6
6
 
@@ -16,3 +16,9 @@ TRANSACTION_RETRIES = Counter(
16
16
  "Number of transaction that have been retried due to transcient error",
17
17
  labelnames=["name"],
18
18
  )
19
+
20
+ CONNECTION_POOL_USAGE = Gauge(
21
+ f"{METRIC_PREFIX}_last_connection_pool_usage",
22
+ "Number of last known active connections in the pool",
23
+ labelnames=["address"],
24
+ )
@@ -8,6 +8,7 @@ from starlette.background import BackgroundTasks
8
8
  from infrahub.core import registry
9
9
  from infrahub.core.timestamp import Timestamp
10
10
  from infrahub.exceptions import InitializationError
11
+ from infrahub.graphql.resolvers.many_relationship import ManyRelationshipResolver
11
12
  from infrahub.graphql.resolvers.single_relationship import SingleRelationshipResolver
12
13
  from infrahub.permissions import PermissionManager
13
14
 
@@ -35,6 +36,7 @@ class GraphqlContext:
35
36
  branch: Branch
36
37
  types: dict
37
38
  single_relationship_resolver: SingleRelationshipResolver
39
+ many_relationship_resolver: ManyRelationshipResolver
38
40
  at: Timestamp | None = None
39
41
  related_node_ids: set | None = None
40
42
  service: InfrahubServices | None = None
@@ -107,6 +109,7 @@ async def prepare_graphql_params(
107
109
  db=db,
108
110
  branch=branch,
109
111
  single_relationship_resolver=SingleRelationshipResolver(),
112
+ many_relationship_resolver=ManyRelationshipResolver(),
110
113
  at=Timestamp(at),
111
114
  types=gqlm.get_graphql_types(),
112
115
  related_node_ids=set(),
@@ -10,6 +10,8 @@ from infrahub.core.node import Node
10
10
  from infrahub.core.timestamp import Timestamp
11
11
  from infrahub.database import InfrahubDatabase
12
12
 
13
+ from .shared import to_frozen_set
14
+
13
15
 
14
16
  @dataclass
15
17
  class GetManyParams:
@@ -68,15 +70,3 @@ class NodeDataLoader(DataLoader[str, Node | None]):
68
70
  for node_id in keys:
69
71
  results.append(nodes_by_id.get(node_id, None))
70
72
  return results
71
-
72
-
73
- def to_frozen_set(to_freeze: dict[str, Any]) -> frozenset:
74
- freezing_dict = {}
75
- for k, v in to_freeze.items():
76
- if isinstance(v, dict):
77
- freezing_dict[k] = to_frozen_set(v)
78
- elif isinstance(v, (list, set)):
79
- freezing_dict[k] = frozenset(v)
80
- else:
81
- freezing_dict[k] = v
82
- return frozenset(freezing_dict)
@@ -0,0 +1,77 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from aiodataloader import DataLoader
5
+
6
+ from infrahub.core.branch.models import Branch
7
+ from infrahub.core.manager import NodeManager
8
+ from infrahub.core.relationship.model import Relationship
9
+ from infrahub.core.schema.relationship_schema import RelationshipSchema
10
+ from infrahub.core.timestamp import Timestamp
11
+ from infrahub.database import InfrahubDatabase
12
+
13
+ from .shared import to_frozen_set
14
+
15
+
16
+ @dataclass
17
+ class QueryPeerParams:
18
+ branch: Branch | str
19
+ source_kind: str
20
+ schema: RelationshipSchema
21
+ filters: dict[str, Any]
22
+ fields: dict | None = None
23
+ at: Timestamp | str | None = None
24
+ branch_agnostic: bool = False
25
+
26
+ def __hash__(self) -> int:
27
+ frozen_fields: frozenset | None = None
28
+ if self.fields:
29
+ frozen_fields = to_frozen_set(self.fields)
30
+ frozen_filters = to_frozen_set(self.filters)
31
+ timestamp = Timestamp(self.at)
32
+ branch = self.branch.name if isinstance(self.branch, Branch) else self.branch
33
+ hash_str = "|".join(
34
+ [
35
+ str(hash(frozen_fields)),
36
+ str(hash(frozen_filters)),
37
+ timestamp.to_string(),
38
+ branch,
39
+ self.schema.name,
40
+ str(self.source_kind),
41
+ str(self.branch_agnostic),
42
+ ]
43
+ )
44
+ return hash(hash_str)
45
+
46
+
47
+ class PeerRelationshipsDataLoader(DataLoader[str, list[Relationship]]):
48
+ def __init__(self, db: InfrahubDatabase, query_params: QueryPeerParams, *args: Any, **kwargs: Any) -> None:
49
+ super().__init__(*args, **kwargs)
50
+ self.query_params = query_params
51
+ self.db = db
52
+
53
+ async def batch_load_fn(self, keys: list[Any]) -> list[list[Relationship]]: # pylint: disable=method-hidden
54
+ async with self.db.start_session() as db:
55
+ peer_rels = await NodeManager.query_peers(
56
+ db=db,
57
+ ids=keys,
58
+ source_kind=self.query_params.source_kind,
59
+ schema=self.query_params.schema,
60
+ filters=self.query_params.filters,
61
+ fields=self.query_params.fields,
62
+ at=self.query_params.at,
63
+ branch=self.query_params.branch,
64
+ branch_agnostic=self.query_params.branch_agnostic,
65
+ fetch_peers=True,
66
+ )
67
+ peer_rels_by_node_id: dict[str, list[Relationship]] = {}
68
+ for rel in peer_rels:
69
+ node_id = rel.node_id
70
+ if node_id not in peer_rels_by_node_id:
71
+ peer_rels_by_node_id[node_id] = []
72
+ peer_rels_by_node_id[node_id].append(rel)
73
+
74
+ results = []
75
+ for node_id in keys:
76
+ results.append(peer_rels_by_node_id.get(node_id, []))
77
+ return results
@@ -0,0 +1,13 @@
1
+ from typing import Any
2
+
3
+
4
+ def to_frozen_set(to_freeze: dict[str, Any]) -> frozenset:
5
+ freezing_dict = {}
6
+ for k, v in to_freeze.items():
7
+ if isinstance(v, dict):
8
+ freezing_dict[k] = to_frozen_set(v)
9
+ elif isinstance(v, (list, set)):
10
+ freezing_dict[k] = frozenset(v)
11
+ else:
12
+ freezing_dict[k] = v
13
+ return frozenset(freezing_dict)
@@ -0,0 +1,264 @@
1
+ from typing import TYPE_CHECKING, Any
2
+
3
+ from graphql import GraphQLResolveInfo
4
+ from infrahub_sdk.utils import deep_merge_dict, extract_fields
5
+
6
+ from infrahub.core.branch.models import Branch
7
+ from infrahub.core.constants import BranchSupportType, RelationshipHierarchyDirection
8
+ from infrahub.core.manager import NodeManager
9
+ from infrahub.core.query.node import NodeGetHierarchyQuery
10
+ from infrahub.core.schema.node_schema import NodeSchema
11
+ from infrahub.core.schema.relationship_schema import RelationshipSchema
12
+ from infrahub.core.timestamp import Timestamp
13
+ from infrahub.database import InfrahubDatabase
14
+
15
+ from ..loaders.peers import PeerRelationshipsDataLoader, QueryPeerParams
16
+ from ..types import RELATIONS_PROPERTY_MAP, RELATIONS_PROPERTY_MAP_REVERSED
17
+
18
+ if TYPE_CHECKING:
19
+ from infrahub.core.schema import MainSchemaTypes
20
+
21
+ from ..initialization import GraphqlContext
22
+
23
+
24
+ class ManyRelationshipResolver:
25
+ def __init__(self) -> None:
26
+ self._data_loader_instances: dict[QueryPeerParams, PeerRelationshipsDataLoader] = {}
27
+
28
+ async def get_descendant_ids(
29
+ self,
30
+ db: InfrahubDatabase,
31
+ branch: Branch,
32
+ at: Timestamp | None,
33
+ parent_id: str,
34
+ node_schema: NodeSchema,
35
+ ) -> list[str]:
36
+ async with db.start_session() as dbs:
37
+ query = await NodeGetHierarchyQuery.init(
38
+ db=dbs,
39
+ direction=RelationshipHierarchyDirection.DESCENDANTS,
40
+ node_id=parent_id,
41
+ node_schema=node_schema,
42
+ at=at,
43
+ branch=branch,
44
+ )
45
+ await query.execute(db=dbs)
46
+ return list(query.get_peer_ids())
47
+
48
+ async def get_peer_count(
49
+ self,
50
+ db: InfrahubDatabase,
51
+ branch: Branch,
52
+ at: Timestamp | None,
53
+ ids: list[str],
54
+ source_kind: str,
55
+ rel_schema: RelationshipSchema,
56
+ filters: dict[str, Any],
57
+ ) -> int:
58
+ async with db.start_session() as dbs:
59
+ return await NodeManager.count_peers(
60
+ db=dbs,
61
+ ids=ids,
62
+ source_kind=source_kind,
63
+ schema=rel_schema,
64
+ filters=filters,
65
+ at=at,
66
+ branch=branch,
67
+ branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
68
+ )
69
+
70
+ async def resolve(
71
+ self,
72
+ parent: dict,
73
+ info: GraphQLResolveInfo,
74
+ include_descendants: bool = False,
75
+ offset: int | None = None,
76
+ limit: int | None = None,
77
+ **kwargs: Any,
78
+ ) -> dict[str, Any]:
79
+ """Resolver for relationships of cardinality=one for Edged responses
80
+
81
+ This resolver is used for paginated responses and as such we redefined the requested
82
+ fields by only reusing information below the 'node' key.
83
+ """
84
+ # Extract the InfraHub schema by inspecting the GQL Schema
85
+
86
+ node_schema: MainSchemaTypes = info.parent_type.graphene_type._meta.schema # type: ignore[attr-defined]
87
+
88
+ context: GraphqlContext = info.context
89
+
90
+ # Extract the name of the fields in the GQL query
91
+ fields = await extract_fields(info.field_nodes[0].selection_set)
92
+ edges = fields.get("edges", {})
93
+ node_fields = edges.get("node", {})
94
+ property_fields = edges.get("properties", {})
95
+ for key, value in property_fields.items():
96
+ mapped_name = RELATIONS_PROPERTY_MAP[key]
97
+ node_fields[mapped_name] = value
98
+
99
+ filters = {
100
+ f"{info.field_name}__{key}": value
101
+ for key, value in kwargs.items()
102
+ if "__" in key and value or key in ["id", "ids"]
103
+ }
104
+
105
+ response: dict[str, Any] = {"edges": [], "count": None}
106
+
107
+ # Extract the schema of the node on the other end of the relationship from the GQL Schema
108
+ node_rel = node_schema.get_relationship(info.field_name)
109
+ source_kind = node_schema.kind
110
+ ids = [parent["id"]]
111
+ if isinstance(node_schema, NodeSchema):
112
+ if node_schema.hierarchy:
113
+ source_kind = node_schema.hierarchy
114
+
115
+ if include_descendants:
116
+ descendant_ids = await self.get_descendant_ids(
117
+ db=context.db,
118
+ branch=context.branch,
119
+ at=context.at,
120
+ parent_id=ids[0],
121
+ node_schema=node_schema,
122
+ )
123
+ ids.extend(descendant_ids)
124
+
125
+ if "count" in fields:
126
+ peer_count = await self.get_peer_count(
127
+ db=context.db,
128
+ branch=context.branch,
129
+ at=context.at,
130
+ ids=ids,
131
+ source_kind=source_kind,
132
+ rel_schema=node_rel,
133
+ filters=filters,
134
+ )
135
+ response["count"] = peer_count
136
+
137
+ if not node_fields:
138
+ return response
139
+
140
+ if offset or limit:
141
+ node_graph = await self._get_entities_simple(
142
+ db=context.db,
143
+ branch=context.branch,
144
+ ids=ids,
145
+ at=context.at,
146
+ related_node_ids=context.related_node_ids,
147
+ source_kind=source_kind,
148
+ rel_schema=node_rel,
149
+ filters=filters,
150
+ node_fields=node_fields,
151
+ offset=offset,
152
+ limit=limit,
153
+ )
154
+ else:
155
+ node_graph = await self._get_entities_with_data_loader(
156
+ db=context.db,
157
+ branch=context.branch,
158
+ ids=ids,
159
+ at=context.at,
160
+ related_node_ids=context.related_node_ids,
161
+ source_kind=source_kind,
162
+ rel_schema=node_rel,
163
+ filters=filters,
164
+ node_fields=node_fields,
165
+ )
166
+
167
+ if not node_graph:
168
+ return response
169
+
170
+ entries = []
171
+ for node in node_graph:
172
+ entry: dict[str, dict[str, Any]] = {"node": {}, "properties": {}}
173
+ for key, mapped in RELATIONS_PROPERTY_MAP_REVERSED.items():
174
+ value = node.pop(key, None)
175
+ if value:
176
+ entry["properties"][mapped] = value
177
+ entry["node"] = node
178
+ entries.append(entry)
179
+
180
+ response["edges"] = entries
181
+ return response
182
+
183
+ async def _get_entities_simple(
184
+ self,
185
+ db: InfrahubDatabase,
186
+ branch: Branch,
187
+ ids: list[str],
188
+ at: Timestamp | None,
189
+ related_node_ids: set[str] | None,
190
+ source_kind: str,
191
+ rel_schema: RelationshipSchema,
192
+ filters: dict[str, Any],
193
+ node_fields: dict[str, Any],
194
+ offset: int | None = None,
195
+ limit: int | None = None,
196
+ ) -> list[dict[str, Any]] | None:
197
+ async with db.start_session() as dbs:
198
+ objs = await NodeManager.query_peers(
199
+ db=dbs,
200
+ ids=ids,
201
+ source_kind=source_kind,
202
+ schema=rel_schema,
203
+ filters=filters,
204
+ fields=node_fields,
205
+ offset=offset,
206
+ limit=limit,
207
+ at=at,
208
+ branch=branch,
209
+ branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
210
+ fetch_peers=True,
211
+ )
212
+ if not objs:
213
+ return None
214
+ return [await obj.to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids) for obj in objs]
215
+
216
+ async def _get_entities_with_data_loader(
217
+ self,
218
+ db: InfrahubDatabase,
219
+ branch: Branch,
220
+ ids: list[str],
221
+ at: Timestamp | None,
222
+ related_node_ids: set[str] | None,
223
+ source_kind: str,
224
+ rel_schema: RelationshipSchema,
225
+ filters: dict[str, Any],
226
+ node_fields: dict[str, Any],
227
+ ) -> list[dict[str, Any]] | None:
228
+ if node_fields and "display_label" in node_fields:
229
+ schema_branch = db.schema.get_schema_branch(name=branch.name)
230
+ display_label_fields = schema_branch.generate_fields_for_display_label(name=rel_schema.peer)
231
+ if display_label_fields:
232
+ node_fields = deep_merge_dict(dicta=node_fields, dictb=display_label_fields)
233
+
234
+ if node_fields and "hfid" in node_fields:
235
+ peer_schema = db.schema.get(name=rel_schema.peer, branch=branch, duplicate=False)
236
+ hfid_fields = peer_schema.generate_fields_for_hfid()
237
+ if hfid_fields:
238
+ node_fields = deep_merge_dict(dicta=node_fields, dictb=hfid_fields)
239
+
240
+ query_params = QueryPeerParams(
241
+ branch=branch,
242
+ source_kind=source_kind,
243
+ schema=rel_schema,
244
+ filters=filters,
245
+ fields=node_fields,
246
+ at=at,
247
+ branch_agnostic=rel_schema.branch is BranchSupportType.AGNOSTIC,
248
+ )
249
+ if query_params in self._data_loader_instances:
250
+ loader = self._data_loader_instances[query_params]
251
+ else:
252
+ loader = PeerRelationshipsDataLoader(db=db, query_params=query_params)
253
+ self._data_loader_instances[query_params] = loader
254
+ all_peer_rels = []
255
+ for node_id in ids:
256
+ node_peer_rels = await loader.load(key=node_id)
257
+ all_peer_rels.extend(node_peer_rels)
258
+ if not all_peer_rels:
259
+ return None
260
+ async with db.start_session() as dbs:
261
+ return [
262
+ await obj.to_graphql(db=dbs, fields=node_fields, related_node_ids=related_node_ids)
263
+ for obj in all_peer_rels
264
+ ]
@@ -6,13 +6,11 @@ from infrahub_sdk.utils import extract_fields
6
6
 
7
7
  from infrahub.core.constants import BranchSupportType, InfrahubKind, RelationshipHierarchyDirection
8
8
  from infrahub.core.manager import NodeManager
9
- from infrahub.core.query.node import NodeGetHierarchyQuery
10
9
  from infrahub.exceptions import NodeNotFoundError
11
10
 
12
11
  from ..models import OrderModel
13
12
  from ..parser import extract_selection
14
13
  from ..permissions import get_permissions
15
- from ..types import RELATIONS_PROPERTY_MAP, RELATIONS_PROPERTY_MAP_REVERSED
16
14
 
17
15
  if TYPE_CHECKING:
18
16
  from graphql import GraphQLResolveInfo
@@ -217,109 +215,11 @@ async def single_relationship_resolver(parent: dict, info: GraphQLResolveInfo, *
217
215
 
218
216
 
219
217
  async def many_relationship_resolver(
220
- parent: dict, info: GraphQLResolveInfo, include_descendants: Optional[bool] = False, **kwargs: Any
218
+ parent: dict, info: GraphQLResolveInfo, include_descendants: bool | None = False, **kwargs: Any
221
219
  ) -> dict[str, Any]:
222
- """Resolver for relationships of cardinality=many for Edged responses
223
-
224
- This resolver is used for paginated responses and as such we redefined the requested
225
- fields by only reusing information below the 'node' key.
226
- """
227
- # Extract the InfraHub schema by inspecting the GQL Schema
228
- node_schema: NodeSchema = info.parent_type.graphene_type._meta.schema
229
-
230
220
  context: GraphqlContext = info.context
231
-
232
- # Extract the name of the fields in the GQL query
233
- fields = await extract_fields(info.field_nodes[0].selection_set)
234
- edges = fields.get("edges", {})
235
- node_fields = edges.get("node", {})
236
- property_fields = edges.get("properties", {})
237
- for key, value in property_fields.items():
238
- mapped_name = RELATIONS_PROPERTY_MAP[key]
239
- node_fields[mapped_name] = value
240
-
241
- # Extract the schema of the node on the other end of the relationship from the GQL Schema
242
- node_rel = node_schema.get_relationship(info.field_name)
243
-
244
- # Extract only the filters from the kwargs and prepend the name of the field to the filters
245
- offset = kwargs.pop("offset", None)
246
- limit = kwargs.pop("limit", None)
247
-
248
- filters = {
249
- f"{info.field_name}__{key}": value
250
- for key, value in kwargs.items()
251
- if "__" in key and value or key in ["id", "ids"]
252
- }
253
-
254
- response: dict[str, Any] = {"edges": [], "count": None}
255
-
256
- source_kind = node_schema.kind
257
-
258
- async with context.db.start_session() as db:
259
- ids = [parent["id"]]
260
- if include_descendants:
261
- query = await NodeGetHierarchyQuery.init(
262
- db=db,
263
- direction=RelationshipHierarchyDirection.DESCENDANTS,
264
- node_id=parent["id"],
265
- node_schema=node_schema,
266
- at=context.at,
267
- branch=context.branch,
268
- )
269
- if node_schema.hierarchy:
270
- source_kind = node_schema.hierarchy
271
- await query.execute(db=db)
272
- descendants_ids = list(query.get_peer_ids())
273
- ids.extend(descendants_ids)
274
-
275
- if "count" in fields:
276
- response["count"] = await NodeManager.count_peers(
277
- db=db,
278
- ids=ids,
279
- source_kind=source_kind,
280
- schema=node_rel,
281
- filters=filters,
282
- at=context.at,
283
- branch=context.branch,
284
- branch_agnostic=node_rel.branch is BranchSupportType.AGNOSTIC,
285
- )
286
-
287
- if not node_fields:
288
- return response
289
-
290
- objs = await NodeManager.query_peers(
291
- db=db,
292
- ids=ids,
293
- source_kind=source_kind,
294
- schema=node_rel,
295
- filters=filters,
296
- fields=node_fields,
297
- offset=offset,
298
- limit=limit,
299
- at=context.at,
300
- branch=context.branch,
301
- branch_agnostic=node_rel.branch is BranchSupportType.AGNOSTIC,
302
- fetch_peers=True,
303
- )
304
-
305
- if not objs:
306
- return response
307
- node_graph = [
308
- await obj.to_graphql(db=db, fields=node_fields, related_node_ids=context.related_node_ids) for obj in objs
309
- ]
310
-
311
- entries = []
312
- for node in node_graph:
313
- entry = {"node": {}, "properties": {}}
314
- for key, mapped in RELATIONS_PROPERTY_MAP_REVERSED.items():
315
- value = node.pop(key, None)
316
- if value:
317
- entry["properties"][mapped] = value
318
- entry["node"] = node
319
- entries.append(entry)
320
- response["edges"] = entries
321
-
322
- return response
221
+ resolver = context.many_relationship_resolver
222
+ return await resolver.resolve(parent=parent, info=info, include_descendants=include_descendants, **kwargs)
323
223
 
324
224
 
325
225
  async def ancestors_resolver(parent: dict, info: GraphQLResolveInfo, **kwargs) -> dict[str, Any]:
@@ -8,6 +8,7 @@ from infrahub.core.constants import InfrahubKind
8
8
  from infrahub.core.manager import NodeManager
9
9
  from infrahub.core.protocols import CoreGraphQLQuery
10
10
  from infrahub.core.timestamp import Timestamp
11
+ from infrahub.graphql.resolvers.many_relationship import ManyRelationshipResolver
11
12
  from infrahub.graphql.resolvers.single_relationship import SingleRelationshipResolver
12
13
  from infrahub.log import get_logger
13
14
 
@@ -48,6 +49,7 @@ async def resolver_graphql_query(
48
49
  related_node_ids=set(),
49
50
  types=context.types,
50
51
  single_relationship_resolver=SingleRelationshipResolver(),
52
+ many_relationship_resolver=ManyRelationshipResolver(),
51
53
  ),
52
54
  root_value=None,
53
55
  variable_values=params or {},
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: infrahub-server
3
- Version: 1.1.8
3
+ Version: 1.1.9
4
4
  Summary: Infrahub is taking a new approach to Infrastructure Management by providing a new generation of datastore to organize and control all the data that defines how an infrastructure should run.
5
5
  Home-page: https://opsmill.com
6
6
  License: AGPL-3.0-only
@@ -33,7 +33,7 @@ infrahub/computed_attribute/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
33
33
  infrahub/computed_attribute/constants.py,sha256=oTMPEfRuf2mcfCkBpRLWRALO6nsLHpFm9jJGu0lowS4,446
34
34
  infrahub/computed_attribute/models.py,sha256=icUzsu0DrGoxMkBVXpNiv17rMo0OwpSE-QBJyWblMM0,2637
35
35
  infrahub/computed_attribute/tasks.py,sha256=rzzWBKrmCQ_ZXJ4Uv5XE7IDhdx39I0_HwyDTLYwkLKo,33238
36
- infrahub/config.py,sha256=DQFXa6e6nbW6McdLVViuCcKYlGjw9ytmYDsKU5l-Jdo,33605
36
+ infrahub/config.py,sha256=lAf4zimE5IBU7FiQGDJAKRdBnbWawpGzcELwv6yMs8Y,33912
37
37
  infrahub/core/__init__.py,sha256=z6EJBZyCYCBqinoBtX9li6BTBbbGV8WCkE_4CrEsmDA,104
38
38
  infrahub/core/account.py,sha256=sggpuO_QpwYH7wXG_lZDnrB5Izmej486o_CYiYjYin8,26497
39
39
  infrahub/core/attribute.py,sha256=H0vHLoNlp1KHI2Jw-Ti5FLmlwJqpYDcv4-9o3K1AsR8,42150
@@ -91,7 +91,7 @@ infrahub/core/diff/query/field_summary.py,sha256=ZXYxX5f-nJ0rG_ZzfSxmF01OmkBYdSj
91
91
  infrahub/core/diff/query/filters.py,sha256=HnbeTo3W2n0gQcKYM4my7sQ0Syoj7aPy5WFkVp38qLc,3646
92
92
  infrahub/core/diff/query/get_conflict_query.py,sha256=fXJNx5hiZW3NLAbGITvC-iuRDhRkXFhLF3oRTOzgEqM,876
93
93
  infrahub/core/diff/query/has_conflicts_query.py,sha256=nftOmnAtDsNxxhpEoLcbQdalhgqx5ROckabou1CSv-Y,2531
94
- infrahub/core/diff/query/merge.py,sha256=HZgsf9tMIEZ_O-ztWBmF-L75nAO9LOe1lKo53Ehub9I,22902
94
+ infrahub/core/diff/query/merge.py,sha256=mk80O6Rsk7-P27C6YiOI96pS4smLqMuNfVFyo-vxXIY,23369
95
95
  infrahub/core/diff/query/merge_tracking_id.py,sha256=puDyqQv-g0jHA-ZuNNwHyJ2spbWOUoSZr131d0NOu0M,817
96
96
  infrahub/core/diff/query/roots_metadata.py,sha256=mwS8elSVxH-xHYdMd0maAGJ94sGY9oXk85jp59RsCQ4,2166
97
97
  infrahub/core/diff/query/save.py,sha256=tfeWCy5FdBgNcPWIO86NZb5DyemX18fLSCDqJ86zsCo,21617
@@ -104,7 +104,7 @@ infrahub/core/diff/repository/deserializer.py,sha256=gkFPByBY1nnmA6sjTJer76RWvwH
104
104
  infrahub/core/diff/repository/repository.py,sha256=xgTzmd_fdc-n7iX8E83sd3fOz25O4P3CEDQFpRMZjpI,24946
105
105
  infrahub/core/diff/tasks.py,sha256=0EuasZKVk76ECpv5yKuqJ7_kGoplqx-_x7w-Di9IAsY,3018
106
106
  infrahub/core/enums.py,sha256=5wMcX9x6acU9CTa4B4b6rFwgRZ31N9c9TR3n2EO0BuI,490
107
- infrahub/core/graph/__init__.py,sha256=Exfs4Nq05DCpF2AkOkBelW_MeAF7Te0NRo0FEhOQHl8,19
107
+ infrahub/core/graph/__init__.py,sha256=MvHNUs4wiJa0gTdCMF6WObMfqStA5V391r31Y9RIVtE,19
108
108
  infrahub/core/graph/constraints.py,sha256=lmuzrKDFoeSKRiLtycB9PXi6zhMYghczKrPYvfWyy90,10396
109
109
  infrahub/core/graph/index.py,sha256=oR6wyYpJbq2IVVzUdiuGyWA511hw2AvgklFoBmQk-bM,1619
110
110
  infrahub/core/graph/schema.py,sha256=FmEPPb1XOFv3nnS_XJCuUqlp8HsStX5A2frHjlhoqvE,10105
@@ -123,7 +123,7 @@ infrahub/core/ipam/utilization.py,sha256=Urv0thyR6xYgwyQaZDnx170Wcw8nKKZkBymwNTM
123
123
  infrahub/core/manager.py,sha256=e2rawz77UG3tLOz3J-JL6-UpJDyJui3uSDg_APnFnAE,46544
124
124
  infrahub/core/merge.py,sha256=ibXM0Rb8qVoBuGiW8q6JYdFsLQ9DS-PPTBoK4R2mPhg,10354
125
125
  infrahub/core/migrations/__init__.py,sha256=PBewY3fZkqVMABRo_oTZkDtdD7HfCC9nCn-DXtTca1g,1150
126
- infrahub/core/migrations/graph/__init__.py,sha256=f1OGl0qaYxr9Rotk3bl08fZ8CrBW6NO2TfT_axD6u7Y,2178
126
+ infrahub/core/migrations/graph/__init__.py,sha256=jKTE6FK5RRuudsnal6cL1wXoy7pAHUR3k6GZGBMFlqQ,2251
127
127
  infrahub/core/migrations/graph/m001_add_version_to_graph.py,sha256=x_dYBnrZtNQiB6TSl4xwXR-Phn7-4EgrJce76ZfPe_c,1499
128
128
  infrahub/core/migrations/graph/m002_attribute_is_default.py,sha256=m0uW3rkDjcYEmuHcTH8ngWxenJ5K0_TkVV00L-Ec6Mw,1004
129
129
  infrahub/core/migrations/graph/m003_relationship_parent_optional.py,sha256=69RIuLB6YoNOOJ9og1DuUxuWSdTbxbSv6uk25-KsHUI,2313
@@ -144,6 +144,7 @@ infrahub/core/migrations/graph/m017_add_core_profile.py,sha256=T-IrK3DW7m_xS4xp3
144
144
  infrahub/core/migrations/graph/m018_uniqueness_nulls.py,sha256=QPQT9ID6idIXizDG0xtzfob_XHG_5LNdv90qD7Lwjng,4785
145
145
  infrahub/core/migrations/graph/m019_restore_rels_to_time.py,sha256=H0pQLkn-iup_-dl7S-jA6BHAeAmz5oBx9IqVLIfcMkw,11718
146
146
  infrahub/core/migrations/graph/m020_duplicate_edges.py,sha256=ec5Z_HF_5MKEQ6gAEvzRM8sxdAsD3lg3eR96fMQ-2lI,6722
147
+ infrahub/core/migrations/graph/m021_missing_hierarchy_merge.py,sha256=JSfkkuqvmSwlXDgyAN__Ypn4J5qhCUPdzAKiaZgqWYU,1606
147
148
  infrahub/core/migrations/query/__init__.py,sha256=JoWOUWlV6IzwxWxObsfCnAAKUOHJkE7dZlOsfB64ZEo,876
148
149
  infrahub/core/migrations/query/attribute_add.py,sha256=gLibqL1TKtt8ia1UQBxL8vyVDibUPlP3w_vp5bp4XsQ,3507
149
150
  infrahub/core/migrations/query/attribute_rename.py,sha256=lotPE_XRqyJQisnrgXtH_cg-0qHMqhtUlT_xcRP84TU,6955
@@ -186,7 +187,7 @@ infrahub/core/query/branch.py,sha256=OmYS1n1U4NEbDXBrPAM9lvh_qb_prbRdNnC07N1k-Mw
186
187
  infrahub/core/query/delete.py,sha256=BsCeUb11Le3NU2hLMcu05acvWrRVrGncxGxpkWTIzJE,1902
187
188
  infrahub/core/query/diff.py,sha256=73Jy-D1wd6GYpUoRb7MGDMkgSuX6KeQ_8swaYLhgJQU,31942
188
189
  infrahub/core/query/ipam.py,sha256=xWCemBaiOA00eU3sB8XmOdNOJ1L69Lls3L2GIw5P_CU,28123
189
- infrahub/core/query/node.py,sha256=mPEChMQe5ivOxPBzLpWeHW3tJT5-oWn0PIgsm5TW05o,61092
190
+ infrahub/core/query/node.py,sha256=nA7o6ZvPMY4Jx1C-LBzqmF_BTNPs_FI7gFuB11lyWe4,61188
190
191
  infrahub/core/query/relationship.py,sha256=Fzg9FvbFj9KsvlzWSwmOj2Ae9qPoaFJa4mzYe77D1qE,38628
191
192
  infrahub/core/query/resource_manager.py,sha256=3B-4-gTNomAyvkw88KHwHbjClIp4SlSJL0cGb-dZVyw,12568
192
193
  infrahub/core/query/standard_node.py,sha256=Wh9ekHhfamxizri_1M5EQFcgJWyIvtAMQRz9nEKOL3w,4437
@@ -266,12 +267,12 @@ infrahub/core/validators/uniqueness/checker.py,sha256=IqreVcMPZj7nNhC5cc5SlBSZ-P
266
267
  infrahub/core/validators/uniqueness/index.py,sha256=yu-clITQF4MrgK36hsyuXllvR4QkVTqy4ugi_Y_C_Sg,5081
267
268
  infrahub/core/validators/uniqueness/model.py,sha256=EPl8X91BSGXGU7GWbUSue6laNGhAtIiXj7rFaz56Kvk,5197
268
269
  infrahub/core/validators/uniqueness/query.py,sha256=DigQCR5278wBiXNXhy1FFTTVQewlf2Q8rCcuSmyilkQ,10171
269
- infrahub/database/__init__.py,sha256=NJfVx7yXiSBiolqU4JcSH3jDYrjjL9uDQVvHa-6cROo,20196
270
+ infrahub/database/__init__.py,sha256=lF6RbdP0huD_XG9Y168yo6lBZBVHL93JsYFE_JuygzI,20766
270
271
  infrahub/database/constants.py,sha256=WmV1iuOk4xulxZHOVvO3sS_VF1eTf7fKh0TPe_RnfV4,507
271
272
  infrahub/database/index.py,sha256=y0sWXO3tdIr1wL1XC9O6iNRV-Elu2KAXFOiYXRIIhN4,1659
272
273
  infrahub/database/manager.py,sha256=BDXNw1RNBeSFV-EZd0aGFbPNuoqlKwrkDqmYB7sy4tU,317
273
274
  infrahub/database/memgraph.py,sha256=Jj5f8Rq2eNx1OSUiLVf8eM0Egf_8HG3wIuZJOPt9hAA,2018
274
- infrahub/database/metrics.py,sha256=lpE81u2glhrqCNj9RRv07GT9jaFhsVKZh9U0Y9OIeNA,560
275
+ infrahub/database/metrics.py,sha256=xU4OSKFbsxcw_yZlt_39PmGtF7S7yPbPuOIlSCu5sI0,739
275
276
  infrahub/database/neo4j.py,sha256=IE5lSevKqu-tJa3KF_bj_gOUx-SpZNdmGOl6oheNhiY,2624
276
277
  infrahub/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
277
278
  infrahub/dependencies/builder/constraint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -370,9 +371,11 @@ infrahub/graphql/auth/query_permission_checker/super_admin_checker.py,sha256=8gC
370
371
  infrahub/graphql/constants.py,sha256=iVvo3HK-ch7YmHw1Eg2E_ja3I45cNAwjpYahsnu85CI,37
371
372
  infrahub/graphql/directives.py,sha256=wyIkJFp7l0J4JqNl1Lqu7YfKXP7glrewlQFMDTUAPcE,645
372
373
  infrahub/graphql/enums.py,sha256=CjGgywkA44MrmTmHxoOoieRPtdmk1bEYzVGxAXUT5as,819
373
- infrahub/graphql/initialization.py,sha256=LMoVOcAeOtnFN6mZV_rLcnjSCBDTY564fLS8TivZ1ak,4055
374
+ infrahub/graphql/initialization.py,sha256=pnXsXHHtmeJghm_yR3ddzk4NNR_nQ379p72Q5uqnddU,4261
374
375
  infrahub/graphql/loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
375
- infrahub/graphql/loaders/node.py,sha256=gPV8tmbmmiusRbLk-wHlOyDIoGBXnpiDYiuKNsXapaM,2969
376
+ infrahub/graphql/loaders/node.py,sha256=sY6mMEG7ApYi3zGEzyIMTkBxMelX3dPGSgDroTSbW8Y,2637
377
+ infrahub/graphql/loaders/peers.py,sha256=fOdgaaPBtBiiLKg8Rws4zXvpyCzUhszgxhDAeXkzETA,2785
378
+ infrahub/graphql/loaders/shared.py,sha256=7O0Jaeb5-urt75XfRQ97a_pgJgs6ZY0TnKAZPUI9eKs,390
376
379
  infrahub/graphql/manager.py,sha256=qF23ffrdbzy4p2crY1RATX8w-AklZkENolM-bNTKJJY,44303
377
380
  infrahub/graphql/metrics.py,sha256=viq_M57mDYd4DDK7suUttf1FJTgzQ3U50yOuSw_Nd-s,2267
378
381
  infrahub/graphql/models.py,sha256=7kr3DSO_rujPocMIfPyZ5Hwy3Mpnu4ySDMAIE9G5Y7Y,147
@@ -416,12 +419,13 @@ infrahub/graphql/queries/status.py,sha256=F3UpRHocUrJRQeOyI1PYa9W-2YDwTBTdGU-CyC
416
419
  infrahub/graphql/queries/task.py,sha256=qBL54BBKRW3vRxg1bIIM88c6Yfrq6IiH2-zc-m-NdTk,3491
417
420
  infrahub/graphql/query.py,sha256=FbYKa9Y5h33jKR8JA4mD3W7FXkCsTd8qWbJ2CdiUXKk,1587
418
421
  infrahub/graphql/resolvers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
419
- infrahub/graphql/resolvers/resolver.py,sha256=GXtO1ik4-msL98MShk-aSzMucLvuKNM5jj936AcOCho,14099
422
+ infrahub/graphql/resolvers/many_relationship.py,sha256=jthzwVi8IZ-ZVpzr0OkFuknd9MD0rWmdRIzSVmigkd0,9587
423
+ infrahub/graphql/resolvers/resolver.py,sha256=1UMP36G467BCkIKjzweGxNfiMzNqNL_QZcY9WuqqElY,10579
420
424
  infrahub/graphql/resolvers/single_relationship.py,sha256=3MtfoR0w-OUcuOkZXVje3BbTTCSPzabh74jSm3tyAPs,6660
421
425
  infrahub/graphql/schema.py,sha256=4M1YYW3yUO99TKsCUDBG2DAyHsvHVoy6eBbRmg7s_Qk,3498
422
426
  infrahub/graphql/subscription/__init__.py,sha256=BaGpWIjP_O8kSHKvWtRQseJtSpcfRlePNPhZf6HrTa0,1189
423
427
  infrahub/graphql/subscription/events.py,sha256=dlawTJljVc7SN5kKag5jOyW1lQuxEBq3PIMSHXQ-09M,1873
424
- infrahub/graphql/subscription/graphql_query.py,sha256=j-W9GzfTH6vIfI0GSMmCiSEYu9Kg6bfjjfGAFIxm7_Q,2044
428
+ infrahub/graphql/subscription/graphql_query.py,sha256=4XimqnKfEF7E318ZzA2rLazeu6_U11sSyq16Itw8c4I,2201
425
429
  infrahub/graphql/types/__init__.py,sha256=oP4DhYAteHkc8LiQsIRHE1q2jaksfc-VvAO1urkmI4k,1895
426
430
  infrahub/graphql/types/attribute.py,sha256=bc2q44q8j5DTNdwBMe0SgG7Rbk8si1h-8SyqGIa-Rn0,6594
427
431
  infrahub/graphql/types/branch.py,sha256=8C07E3ne1Rk0MQtzLchaJwiQqPNDHIVT59t_y1d3wNs,1393
@@ -673,12 +677,12 @@ infrahub_sdk/utils.py,sha256=S2uQvg2WROdOK2gtbQ6HSVWStTVqVkzmEUXcksuMZGI,11513
673
677
  infrahub_sdk/uuidt.py,sha256=Tz-4nHkJwbi39UT3gaIe2wJeZNAoBqf6tm3sw7LZbXc,2155
674
678
  infrahub_sdk/yaml.py,sha256=L_sj5ds-0_uKe3aIfZu86kDLq8tffKzle9dcyDUTaEc,2937
675
679
  infrahub_testcontainers/__init__.py,sha256=oPpmesGgYBSdKTg1L37FGwYBeao1EHury5SJGul-CT8,216
676
- infrahub_testcontainers/container.py,sha256=dtweuZol1LAZiJBItRnB-Yrd-k6ohVuOdcgWcd0F-RM,4855
677
- infrahub_testcontainers/docker-compose.test.yml,sha256=l-ac0Bi8MxXCNM0w7QPakmeMsYzMnJXPhENN1yGRHAk,5993
680
+ infrahub_testcontainers/container.py,sha256=AuHmQv4fqB5Til40-GUsZhop9hzHWLWkqsRV44UZPUY,5870
681
+ infrahub_testcontainers/docker-compose.test.yml,sha256=gDmMNoIh5ROQxVAw02_twaAn_wi3Fw63D1Ek2k8YlWc,5966
678
682
  infrahub_testcontainers/haproxy.cfg,sha256=QF_DJSll10uGWICLnwFpj0-0VWIeB3CteCDthVoWpCU,1316
679
683
  infrahub_testcontainers/helpers.py,sha256=NlzyZN1QpByUQ6t7NwIDQxSSM7xFvY2jCM6-P8RYE6g,5253
680
- infrahub_server-1.1.8.dist-info/LICENSE.txt,sha256=TfPDBt3ar0uv_f9cqCDMZ5rIzW3CY8anRRd4PkL6ejs,34522
681
- infrahub_server-1.1.8.dist-info/METADATA,sha256=BJcVX7r_XMQiEkLZjsOAZo-mVuL7k1prexbpw7eAmrs,8081
682
- infrahub_server-1.1.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
683
- infrahub_server-1.1.8.dist-info/entry_points.txt,sha256=JNQoBcLpUyfeOMhls_-uX1CdJ8Vl-AFSh9UhzTcKdjA,329
684
- infrahub_server-1.1.8.dist-info/RECORD,,
684
+ infrahub_server-1.1.9.dist-info/LICENSE.txt,sha256=TfPDBt3ar0uv_f9cqCDMZ5rIzW3CY8anRRd4PkL6ejs,34522
685
+ infrahub_server-1.1.9.dist-info/METADATA,sha256=N42-nCLMaBJBOiUs29mwCq-Sr_CHvZ3e4WT-RA8R7IU,8081
686
+ infrahub_server-1.1.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
687
+ infrahub_server-1.1.9.dist-info/entry_points.txt,sha256=JNQoBcLpUyfeOMhls_-uX1CdJ8Vl-AFSh9UhzTcKdjA,329
688
+ infrahub_server-1.1.9.dist-info/RECORD,,
@@ -24,6 +24,7 @@ INFRAHUB_SERVICES: dict[str, ContainerService] = {
24
24
 
25
25
  PROJECT_ENV_VARIABLES: dict[str, str] = {
26
26
  "INFRAHUB_TESTING_DOCKER_IMAGE": "registry.opsmill.io/opsmill/infrahub",
27
+ "INFRAHUB_TESTING_DOCKER_ENTRYPOINT": f"gunicorn --config backend/infrahub/serve/gunicorn_config.py -w {os.environ.get("INFRAHUB_TESTING_WEB_CONCURRENCY", 4)} --logger-class infrahub.serve.log.GunicornLogger infrahub.server:app", # noqa: E501
27
28
  "INFRAHUB_TESTING_IMAGE_VERSION": infrahub_version,
28
29
  "INFRAHUB_TESTING_PRODUCTION": "false",
29
30
  "INFRAHUB_TESTING_DB_ADDRESS": "database",
@@ -38,6 +39,7 @@ PROJECT_ENV_VARIABLES: dict[str, str] = {
38
39
  "INFRAHUB_TESTING_BROKER_ADDRESS": "message-queue",
39
40
  "INFRAHUB_TESTING_CACHE_ADDRESS": "cache",
40
41
  "INFRAHUB_TESTING_WORKFLOW_ADDRESS": "task-manager",
42
+ "INFRAHUB_TESTING_WORKFLOW_DEFAULT_WORKER_TYPE": "infrahubasync",
41
43
  "INFRAHUB_TESTING_TIMEOUT": "60",
42
44
  "INFRAHUB_TESTING_PREFECT_API": "http://task-manager:4200/api",
43
45
  "INFRAHUB_TESTING_LOCAL_REMOTE_GIT_DIRECTORY": "repos",
@@ -96,6 +98,15 @@ class InfrahubDockerCompose(DockerCompose):
96
98
  env_file = directory / ".env"
97
99
 
98
100
  PROJECT_ENV_VARIABLES.update({"INFRAHUB_TESTING_IMAGE_VERSION": version})
101
+ if os.environ.get("INFRAHUB_TESTING_ENTERPRISE"):
102
+ PROJECT_ENV_VARIABLES.update(
103
+ {
104
+ "INFRAHUB_TESTING_DOCKER_IMAGE": "registry.opsmill.io/opsmill/infrahub-enterprise",
105
+ "INFRAHUB_TESTING_DOCKER_ENTRYPOINT": f"gunicorn --config community/backend/infrahub/serve/gunicorn_config.py -w {os.environ.get("INFRAHUB_TESTING_WEB_CONCURRENCY", 4)} --logger-class infrahub.serve.log.GunicornLogger infrahub_enterprise.server:app", # noqa: E501
106
+ "INFRAHUB_TESTING_WORKFLOW_DEFAULT_WORKER_TYPE": "infrahubentasync",
107
+ "NEO4J_DOCKER_IMAGE": "neo4j:5.20.0-enterprise",
108
+ }
109
+ )
99
110
 
100
111
  with env_file.open(mode="w", encoding="utf-8") as file:
101
112
  for key, value in PROJECT_ENV_VARIABLES.items():
@@ -102,11 +102,7 @@ services:
102
102
  mode: replicated
103
103
  replicas: ${INFRAHUB_TESTING_API_SERVER_COUNT}
104
104
  image: "${INFRAHUB_TESTING_DOCKER_IMAGE}:${INFRAHUB_TESTING_IMAGE_VERSION}"
105
- command: >
106
- gunicorn --config backend/infrahub/serve/gunicorn_config.py
107
- -w ${INFRAHUB_TESTING_WEB_CONCURRENCY}
108
- --logger-class infrahub.serve.log.GunicornLogger
109
- infrahub.server:app
105
+ command: ${INFRAHUB_TESTING_DOCKER_ENTRYPOINT}
110
106
  environment:
111
107
  INFRAHUB_PRODUCTION: ${INFRAHUB_TESTING_PRODUCTION}
112
108
  INFRAHUB_LOG_LEVEL: ${INFRAHUB_TESTING_LOG_LEVEL:-INFO}
@@ -114,6 +110,7 @@ services:
114
110
  INFRAHUB_CACHE_ADDRESS: ${INFRAHUB_TESTING_CACHE_ADDRESS}
115
111
  INFRAHUB_DB_ADDRESS: ${INFRAHUB_TESTING_DB_ADDRESS}
116
112
  INFRAHUB_WORKFLOW_ADDRESS: ${INFRAHUB_TESTING_WORKFLOW_ADDRESS}
113
+ INFRAHUB_WORKFLOW_DEFAULT_WORKER_TYPE: ${INFRAHUB_TESTING_WORKFLOW_DEFAULT_WORKER_TYPE}
117
114
  INFRAHUB_INITIAL_ADMIN_TOKEN: ${INFRAHUB_TESTING_INITIAL_ADMIN_TOKEN}
118
115
  INFRAHUB_INITIAL_AGENT_TOKEN: ${INFRAHUB_TESTING_INITIAL_AGENT_TOKEN}
119
116
  INFRAHUB_SECURITY_SECRET_KEY: ${INFRAHUB_TESTING_SECURITY_SECRET_KEY}
@@ -142,7 +139,7 @@ services:
142
139
  mode: replicated
143
140
  replicas: ${INFRAHUB_TESTING_TASK_WORKER_COUNT}
144
141
  image: "${INFRAHUB_TESTING_DOCKER_IMAGE}:${INFRAHUB_TESTING_IMAGE_VERSION}"
145
- command: prefect worker start --type infrahubasync --pool infrahub-worker --with-healthcheck
142
+ command: prefect worker start --type ${INFRAHUB_TESTING_WORKFLOW_DEFAULT_WORKER_TYPE} --pool infrahub-worker --with-healthcheck
146
143
  environment:
147
144
  INFRAHUB_PRODUCTION: ${INFRAHUB_TESTING_PRODUCTION}
148
145
  INFRAHUB_LOG_LEVEL: ${INFRAHUB_TESTING_LOG_LEVEL}