infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b0__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 (103) hide show
  1. infrahub/actions/tasks.py +200 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/query.py +2 -0
  4. infrahub/api/schema.py +3 -0
  5. infrahub/auth.py +5 -5
  6. infrahub/cli/db.py +2 -2
  7. infrahub/config.py +7 -2
  8. infrahub/core/attribute.py +22 -19
  9. infrahub/core/branch/models.py +2 -2
  10. infrahub/core/branch/needs_rebase_status.py +11 -0
  11. infrahub/core/branch/tasks.py +2 -2
  12. infrahub/core/constants/__init__.py +1 -0
  13. infrahub/core/convert_object_type/object_conversion.py +201 -0
  14. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  15. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  16. infrahub/core/diff/query/artifact.py +1 -1
  17. infrahub/core/graph/__init__.py +1 -1
  18. infrahub/core/initialization.py +2 -2
  19. infrahub/core/manager.py +3 -81
  20. infrahub/core/migrations/graph/__init__.py +2 -0
  21. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  22. infrahub/core/node/__init__.py +23 -2
  23. infrahub/core/node/create.py +67 -35
  24. infrahub/core/node/lock_utils.py +98 -0
  25. infrahub/core/property.py +11 -0
  26. infrahub/core/protocols.py +1 -0
  27. infrahub/core/query/attribute.py +27 -15
  28. infrahub/core/query/node.py +47 -184
  29. infrahub/core/query/relationship.py +43 -26
  30. infrahub/core/query/subquery.py +0 -8
  31. infrahub/core/relationship/model.py +59 -19
  32. infrahub/core/schema/attribute_schema.py +0 -2
  33. infrahub/core/schema/definitions/core/repository.py +7 -0
  34. infrahub/core/schema/relationship_schema.py +0 -1
  35. infrahub/core/schema/schema_branch.py +3 -2
  36. infrahub/generators/models.py +31 -12
  37. infrahub/generators/tasks.py +3 -1
  38. infrahub/git/base.py +38 -1
  39. infrahub/graphql/api/dependencies.py +2 -4
  40. infrahub/graphql/api/endpoints.py +2 -2
  41. infrahub/graphql/app.py +2 -4
  42. infrahub/graphql/initialization.py +2 -3
  43. infrahub/graphql/manager.py +212 -137
  44. infrahub/graphql/middleware.py +12 -0
  45. infrahub/graphql/mutations/branch.py +11 -0
  46. infrahub/graphql/mutations/computed_attribute.py +110 -3
  47. infrahub/graphql/mutations/convert_object_type.py +34 -13
  48. infrahub/graphql/mutations/ipam.py +21 -8
  49. infrahub/graphql/mutations/main.py +37 -153
  50. infrahub/graphql/mutations/profile.py +195 -0
  51. infrahub/graphql/mutations/proposed_change.py +2 -1
  52. infrahub/graphql/mutations/repository.py +22 -83
  53. infrahub/graphql/mutations/webhook.py +1 -1
  54. infrahub/graphql/registry.py +173 -0
  55. infrahub/graphql/schema.py +4 -1
  56. infrahub/lock.py +52 -26
  57. infrahub/locks/__init__.py +0 -0
  58. infrahub/locks/tasks.py +37 -0
  59. infrahub/patch/plan_writer.py +2 -2
  60. infrahub/profiles/__init__.py +0 -0
  61. infrahub/profiles/node_applier.py +101 -0
  62. infrahub/profiles/queries/__init__.py +0 -0
  63. infrahub/profiles/queries/get_profile_data.py +99 -0
  64. infrahub/profiles/tasks.py +63 -0
  65. infrahub/repositories/__init__.py +0 -0
  66. infrahub/repositories/create_repository.py +113 -0
  67. infrahub/tasks/registry.py +6 -4
  68. infrahub/webhook/models.py +1 -1
  69. infrahub/workflows/catalogue.py +38 -3
  70. infrahub/workflows/models.py +17 -2
  71. infrahub_sdk/branch.py +5 -8
  72. infrahub_sdk/client.py +364 -84
  73. infrahub_sdk/convert_object_type.py +61 -0
  74. infrahub_sdk/ctl/check.py +2 -3
  75. infrahub_sdk/ctl/cli_commands.py +16 -12
  76. infrahub_sdk/ctl/config.py +8 -2
  77. infrahub_sdk/ctl/generator.py +2 -3
  78. infrahub_sdk/ctl/repository.py +39 -1
  79. infrahub_sdk/ctl/schema.py +12 -1
  80. infrahub_sdk/ctl/utils.py +4 -0
  81. infrahub_sdk/ctl/validate.py +5 -3
  82. infrahub_sdk/diff.py +4 -5
  83. infrahub_sdk/exceptions.py +2 -0
  84. infrahub_sdk/graphql.py +7 -2
  85. infrahub_sdk/node/attribute.py +2 -0
  86. infrahub_sdk/node/node.py +28 -20
  87. infrahub_sdk/playback.py +1 -2
  88. infrahub_sdk/protocols.py +40 -6
  89. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  90. infrahub_sdk/pytest_plugin/utils.py +40 -0
  91. infrahub_sdk/repository.py +1 -2
  92. infrahub_sdk/schema/main.py +1 -0
  93. infrahub_sdk/spec/object.py +43 -4
  94. infrahub_sdk/spec/range_expansion.py +118 -0
  95. infrahub_sdk/timestamp.py +18 -6
  96. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/METADATA +6 -9
  97. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/RECORD +102 -84
  98. infrahub_testcontainers/models.py +2 -2
  99. infrahub_testcontainers/performance_test.py +4 -4
  100. infrahub/core/convert_object_type/conversion.py +0 -134
  101. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/LICENSE.txt +0 -0
  102. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/WHEEL +0 -0
  103. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b0.dist-info}/entry_points.txt +0 -0
infrahub_sdk/client.py CHANGED
@@ -4,7 +4,9 @@ import asyncio
4
4
  import copy
5
5
  import logging
6
6
  import time
7
- from collections.abc import Coroutine, MutableMapping
7
+ import warnings
8
+ from collections.abc import Coroutine, Mapping, MutableMapping
9
+ from datetime import datetime
8
10
  from functools import wraps
9
11
  from time import sleep
10
12
  from typing import (
@@ -24,12 +26,14 @@ from typing_extensions import Self
24
26
 
25
27
  from .batch import InfrahubBatch, InfrahubBatchSync
26
28
  from .branch import (
29
+ MUTATION_QUERY_TASK,
27
30
  BranchData,
28
31
  InfrahubBranchManager,
29
32
  InfrahubBranchManagerSync,
30
33
  )
31
34
  from .config import Config
32
35
  from .constants import InfrahubClientMode
36
+ from .convert_object_type import CONVERT_OBJECT_MUTATION, ConversionFieldInput
33
37
  from .data import RepositoryBranchInfo, RepositoryData
34
38
  from .diff import NodeDiff, diff_tree_node_to_node_diff, get_diff_summary_query
35
39
  from .exceptions import (
@@ -60,6 +64,9 @@ from .utils import decode_json, get_user_permissions, is_valid_uuid
60
64
  if TYPE_CHECKING:
61
65
  from types import TracebackType
62
66
 
67
+ from httpx._transports.base import AsyncBaseTransport, BaseTransport
68
+ from httpx._types import ProxyTypes
69
+
63
70
  from .context import RequestContext
64
71
 
65
72
 
@@ -72,6 +79,16 @@ class ProcessRelationsNode(TypedDict):
72
79
  related_nodes: list[InfrahubNode]
73
80
 
74
81
 
82
+ class ProxyConfig(TypedDict):
83
+ proxy: ProxyTypes | None
84
+ mounts: Mapping[str, AsyncBaseTransport | None] | None
85
+
86
+
87
+ class ProxyConfigSync(TypedDict):
88
+ proxy: ProxyTypes | None
89
+ mounts: Mapping[str, BaseTransport | None] | None
90
+
91
+
75
92
  class ProcessRelationsNodeSync(TypedDict):
76
93
  nodes: list[InfrahubNodeSync]
77
94
  related_nodes: list[InfrahubNodeSync]
@@ -105,6 +122,15 @@ def handle_relogin_sync(func: Callable[..., httpx.Response]): # type: ignore[no
105
122
  return wrapper
106
123
 
107
124
 
125
+ def raise_for_error_deprecation_warning(value: bool | None) -> None:
126
+ if value is not None:
127
+ warnings.warn(
128
+ "Using `raise_for_error` is deprecated, use `try/except` to handle errors.",
129
+ DeprecationWarning,
130
+ stacklevel=1,
131
+ )
132
+
133
+
108
134
  class BaseClient:
109
135
  """Base class for InfrahubClient and InfrahubClientSync"""
110
136
 
@@ -209,7 +235,7 @@ class BaseClient:
209
235
  delete_unused_nodes=delete_unused_nodes,
210
236
  group_type=group_type,
211
237
  group_params=group_params,
212
- branch=branch,
238
+ branch=branch or self.default_branch,
213
239
  )
214
240
 
215
241
  def _graphql_url(
@@ -250,7 +276,7 @@ class BaseClient:
250
276
 
251
277
  return Mutation(
252
278
  name="AllocateIPAddress",
253
- mutation="IPAddressPoolGetResource",
279
+ mutation="InfrahubIPAddressPoolGetResource",
254
280
  query={"ok": None, "node": {"id": None, "kind": None, "identifier": None, "display_label": None}},
255
281
  input_data={"data": input_data},
256
282
  )
@@ -281,7 +307,7 @@ class BaseClient:
281
307
 
282
308
  return Mutation(
283
309
  name="AllocateIPPrefix",
284
- mutation="IPPrefixPoolGetResource",
310
+ mutation="InfrahubIPPrefixPoolGetResource",
285
311
  query={"ok": None, "node": {"id": None, "kind": None, "identifier": None, "display_label": None}},
286
312
  input_data={"data": input_data},
287
313
  )
@@ -305,13 +331,11 @@ class InfrahubClient(BaseClient):
305
331
  async def get_version(self) -> str:
306
332
  """Return the Infrahub version."""
307
333
  response = await self.execute_graphql(query="query { InfrahubInfo { version }}")
308
- version = response.get("InfrahubInfo", {}).get("version", "")
309
- return version
334
+ return response.get("InfrahubInfo", {}).get("version", "")
310
335
 
311
336
  async def get_user(self) -> dict:
312
337
  """Return user information"""
313
- user_info = await self.execute_graphql(query=QUERY_USER)
314
- return user_info
338
+ return await self.execute_graphql(query=QUERY_USER)
315
339
 
316
340
  async def get_user_permissions(self) -> dict:
317
341
  """Return user permissions"""
@@ -540,6 +564,7 @@ class InfrahubClient(BaseClient):
540
564
  schema_kind: str,
541
565
  branch: str,
542
566
  prefetch_relationships: bool,
567
+ include: list[str] | None,
543
568
  timeout: int | None = None,
544
569
  ) -> ProcessRelationsNode:
545
570
  """Processes InfrahubNode and their Relationships from the GraphQL query response.
@@ -564,9 +589,12 @@ class InfrahubClient(BaseClient):
564
589
  node = await InfrahubNode.from_graphql(client=self, branch=branch, data=item, timeout=timeout)
565
590
  nodes.append(node)
566
591
 
567
- if prefetch_relationships:
592
+ if prefetch_relationships or (include and any(rel in include for rel in node._relationships)):
568
593
  await node._process_relationships(
569
- node_data=item, branch=branch, related_nodes=related_nodes, timeout=timeout
594
+ node_data=item,
595
+ branch=branch,
596
+ related_nodes=related_nodes,
597
+ timeout=timeout,
570
598
  )
571
599
 
572
600
  return ProcessRelationsNode(nodes=nodes, related_nodes=related_nodes)
@@ -816,6 +844,7 @@ class InfrahubClient(BaseClient):
816
844
  branch=branch,
817
845
  prefetch_relationships=prefetch_relationships,
818
846
  timeout=timeout,
847
+ include=include,
819
848
  )
820
849
  return response, process_result
821
850
 
@@ -881,7 +910,7 @@ class InfrahubClient(BaseClient):
881
910
  branch_name: str | None = None,
882
911
  at: str | Timestamp | None = None,
883
912
  timeout: int | None = None,
884
- raise_for_error: bool = True,
913
+ raise_for_error: bool | None = None,
885
914
  tracker: str | None = None,
886
915
  ) -> dict:
887
916
  """Execute a GraphQL query (or mutation).
@@ -893,13 +922,17 @@ class InfrahubClient(BaseClient):
893
922
  branch_name (str, optional): Name of the branch on which the query will be executed. Defaults to None.
894
923
  at (str, optional): Time when the query should be executed. Defaults to None.
895
924
  timeout (int, optional): Timeout in second for the query. Defaults to None.
896
- raise_for_error (bool, optional): Flag to indicate that we need to raise an exception if the response has some errors. Defaults to True.
925
+ raise_for_error (bool | None, optional): Deprecated. Controls only HTTP status handling.
926
+ - None (default) or True: HTTP errors raise via resp.raise_for_status().
927
+ - False: HTTP errors are not automatically raised. Defaults to None.
928
+
897
929
  Raises:
898
- GraphQLError: _description_
930
+ GraphQLError: When the GraphQL response contains errors.
899
931
 
900
932
  Returns:
901
- _type_: _description_
933
+ dict: The GraphQL data payload (response["data"]).
902
934
  """
935
+ raise_for_error_deprecation_warning(value=raise_for_error)
903
936
 
904
937
  branch_name = branch_name or self.default_branch
905
938
  url = self._graphql_url(branch_name=branch_name, at=at)
@@ -922,7 +955,7 @@ class InfrahubClient(BaseClient):
922
955
  try:
923
956
  resp = await self._post(url=url, payload=payload, headers=headers, timeout=timeout)
924
957
 
925
- if raise_for_error:
958
+ if raise_for_error in (None, True):
926
959
  resp.raise_for_status()
927
960
 
928
961
  retry = False
@@ -958,7 +991,11 @@ class InfrahubClient(BaseClient):
958
991
 
959
992
  @handle_relogin
960
993
  async def _post(
961
- self, url: str, payload: dict, headers: dict | None = None, timeout: int | None = None
994
+ self,
995
+ url: str,
996
+ payload: dict,
997
+ headers: dict | None = None,
998
+ timeout: int | None = None,
962
999
  ) -> httpx.Response:
963
1000
  """Execute a HTTP POST with HTTPX.
964
1001
 
@@ -973,7 +1010,11 @@ class InfrahubClient(BaseClient):
973
1010
  headers.update(base_headers)
974
1011
 
975
1012
  return await self._request(
976
- url=url, method=HTTPMethod.POST, headers=headers, timeout=timeout or self.default_timeout, payload=payload
1013
+ url=url,
1014
+ method=HTTPMethod.POST,
1015
+ headers=headers,
1016
+ timeout=timeout or self.default_timeout,
1017
+ payload=payload,
977
1018
  )
978
1019
 
979
1020
  @handle_relogin
@@ -991,34 +1032,47 @@ class InfrahubClient(BaseClient):
991
1032
  headers.update(base_headers)
992
1033
 
993
1034
  return await self._request(
994
- url=url, method=HTTPMethod.GET, headers=headers, timeout=timeout or self.default_timeout
1035
+ url=url,
1036
+ method=HTTPMethod.GET,
1037
+ headers=headers,
1038
+ timeout=timeout or self.default_timeout,
995
1039
  )
996
1040
 
997
1041
  async def _request(
998
- self, url: str, method: HTTPMethod, headers: dict[str, Any], timeout: int, payload: dict | None = None
1042
+ self,
1043
+ url: str,
1044
+ method: HTTPMethod,
1045
+ headers: dict[str, Any],
1046
+ timeout: int,
1047
+ payload: dict | None = None,
999
1048
  ) -> httpx.Response:
1000
1049
  response = await self._request_method(url=url, method=method, headers=headers, timeout=timeout, payload=payload)
1001
1050
  self._record(response)
1002
1051
  return response
1003
1052
 
1004
1053
  async def _default_request_method(
1005
- self, url: str, method: HTTPMethod, headers: dict[str, Any], timeout: int, payload: dict | None = None
1054
+ self,
1055
+ url: str,
1056
+ method: HTTPMethod,
1057
+ headers: dict[str, Any],
1058
+ timeout: int,
1059
+ payload: dict | None = None,
1006
1060
  ) -> httpx.Response:
1007
1061
  params: dict[str, Any] = {}
1008
1062
  if payload:
1009
1063
  params["json"] = payload
1010
1064
 
1011
- proxy_config: dict[str, str | dict[str, httpx.HTTPTransport]] = {}
1065
+ proxy_config: ProxyConfig = {"proxy": None, "mounts": None}
1012
1066
  if self.config.proxy:
1013
1067
  proxy_config["proxy"] = self.config.proxy
1014
1068
  elif self.config.proxy_mounts.is_set:
1015
1069
  proxy_config["mounts"] = {
1016
- key: httpx.HTTPTransport(proxy=value)
1070
+ key: httpx.AsyncHTTPTransport(proxy=value)
1017
1071
  for key, value in self.config.proxy_mounts.model_dump(by_alias=True).items()
1018
1072
  }
1019
1073
 
1020
1074
  async with httpx.AsyncClient(
1021
- **proxy_config, # type: ignore[arg-type]
1075
+ **proxy_config,
1022
1076
  verify=self.config.tls_ca_file if self.config.tls_ca_file else not self.config.tls_insecure,
1023
1077
  ) as client:
1024
1078
  try:
@@ -1044,7 +1098,10 @@ class InfrahubClient(BaseClient):
1044
1098
  response = await self._request(
1045
1099
  url=url,
1046
1100
  method=HTTPMethod.POST,
1047
- headers={"content-type": "application/json", "Authorization": f"Bearer {self.refresh_token}"},
1101
+ headers={
1102
+ "content-type": "application/json",
1103
+ "Authorization": f"Bearer {self.refresh_token}",
1104
+ },
1048
1105
  timeout=self.default_timeout,
1049
1106
  )
1050
1107
 
@@ -1077,7 +1134,10 @@ class InfrahubClient(BaseClient):
1077
1134
  response = await self._request(
1078
1135
  url=url,
1079
1136
  method=HTTPMethod.POST,
1080
- payload={"username": self.config.username, "password": self.config.password},
1137
+ payload={
1138
+ "username": self.config.username,
1139
+ "password": self.config.password,
1140
+ },
1081
1141
  headers={"content-type": "application/json"},
1082
1142
  timeout=self.default_timeout,
1083
1143
  )
@@ -1099,17 +1159,19 @@ class InfrahubClient(BaseClient):
1099
1159
  at: str | None = None,
1100
1160
  timeout: int | None = None,
1101
1161
  tracker: str | None = None,
1102
- raise_for_error: bool = True,
1162
+ raise_for_error: bool | None = None,
1103
1163
  ) -> dict:
1164
+ raise_for_error_deprecation_warning(value=raise_for_error)
1165
+
1104
1166
  url = f"{self.address}/api/query/{name}"
1105
1167
  url_params = copy.deepcopy(params or {})
1168
+ url_params["branch"] = branch_name or self.default_branch
1169
+
1106
1170
  headers = copy.copy(self.headers or {})
1107
1171
 
1108
1172
  if self.insert_tracker and tracker:
1109
1173
  headers["X-Infrahub-Tracker"] = tracker
1110
1174
 
1111
- if branch_name:
1112
- url_params["branch"] = branch_name
1113
1175
  if at:
1114
1176
  url_params["at"] = at
1115
1177
 
@@ -1145,26 +1207,67 @@ class InfrahubClient(BaseClient):
1145
1207
  timeout=timeout or self.default_timeout,
1146
1208
  )
1147
1209
 
1148
- if raise_for_error:
1210
+ if raise_for_error in (None, True):
1149
1211
  resp.raise_for_status()
1150
1212
 
1151
1213
  return decode_json(response=resp)
1152
1214
 
1215
+ async def create_diff(
1216
+ self,
1217
+ branch: str,
1218
+ name: str,
1219
+ from_time: datetime,
1220
+ to_time: datetime,
1221
+ wait_until_completion: bool = True,
1222
+ ) -> bool | str:
1223
+ if from_time > to_time:
1224
+ raise ValueError("from_time must be <= to_time")
1225
+ input_data = {
1226
+ "wait_until_completion": wait_until_completion,
1227
+ "data": {
1228
+ "name": name,
1229
+ "branch": branch,
1230
+ "from_time": from_time.isoformat(),
1231
+ "to_time": to_time.isoformat(),
1232
+ },
1233
+ }
1234
+
1235
+ mutation_query = MUTATION_QUERY_TASK if not wait_until_completion else {"ok": None}
1236
+ query = Mutation(mutation="DiffUpdate", input_data=input_data, query=mutation_query)
1237
+ response = await self.execute_graphql(query=query.render(), tracker="mutation-diff-update")
1238
+
1239
+ if not wait_until_completion and "task" in response["DiffUpdate"]:
1240
+ return response["DiffUpdate"]["task"]["id"]
1241
+
1242
+ return response["DiffUpdate"]["ok"]
1243
+
1153
1244
  async def get_diff_summary(
1154
1245
  self,
1155
1246
  branch: str,
1247
+ name: str | None = None,
1248
+ from_time: datetime | None = None,
1249
+ to_time: datetime | None = None,
1156
1250
  timeout: int | None = None,
1157
1251
  tracker: str | None = None,
1158
- raise_for_error: bool = True,
1252
+ raise_for_error: bool | None = None,
1159
1253
  ) -> list[NodeDiff]:
1160
1254
  query = get_diff_summary_query()
1255
+ input_data = {"branch_name": branch}
1256
+ if name:
1257
+ input_data["name"] = name
1258
+ if from_time and to_time and from_time > to_time:
1259
+ raise ValueError("from_time must be <= to_time")
1260
+ if from_time:
1261
+ input_data["from_time"] = from_time.isoformat()
1262
+ if to_time:
1263
+ input_data["to_time"] = to_time.isoformat()
1161
1264
  response = await self.execute_graphql(
1162
1265
  query=query,
1163
1266
  branch_name=branch,
1164
1267
  timeout=timeout,
1165
1268
  tracker=tracker,
1166
1269
  raise_for_error=raise_for_error,
1167
- variables={"branch_name": branch},
1270
+ variables=input_data,
1168
1271
  )
1169
1272
 
1170
1273
  node_diffs: list[NodeDiff] = []
@@ -1220,7 +1323,7 @@ class InfrahubClient(BaseClient):
1220
1323
  branch: str | None = ...,
1221
1324
  timeout: int | None = ...,
1222
1325
  tracker: str | None = ...,
1223
- raise_for_error: bool = ...,
1326
+ raise_for_error: bool | None = ...,
1224
1327
  ) -> SchemaType: ...
1225
1328
 
1226
1329
  @overload
@@ -1265,7 +1368,7 @@ class InfrahubClient(BaseClient):
1265
1368
  branch: str | None = ...,
1266
1369
  timeout: int | None = ...,
1267
1370
  tracker: str | None = ...,
1268
- raise_for_error: bool = ...,
1371
+ raise_for_error: bool | None = ...,
1269
1372
  ) -> CoreNode | None: ...
1270
1373
 
1271
1374
  async def allocate_next_ip_address(
@@ -1279,7 +1382,7 @@ class InfrahubClient(BaseClient):
1279
1382
  branch: str | None = None,
1280
1383
  timeout: int | None = None,
1281
1384
  tracker: str | None = None,
1282
- raise_for_error: bool = True,
1385
+ raise_for_error: bool | None = None,
1283
1386
  ) -> CoreNode | SchemaType | None:
1284
1387
  """Allocate a new IP address by using the provided resource pool.
1285
1388
 
@@ -1292,7 +1395,7 @@ class InfrahubClient(BaseClient):
1292
1395
  branch (str, optional): Name of the branch to allocate from. Defaults to default_branch.
1293
1396
  timeout (int, optional): Flag to indicate whether to populate the store with the retrieved nodes.
1294
1397
  tracker (str, optional): The offset for pagination.
1295
- raise_for_error (bool, optional): The limit for pagination.
1398
+ raise_for_error (bool, optional): Deprecated, raise an error if the HTTP status is not 2XX.
1296
1399
  Returns:
1297
1400
  InfrahubNode: Node corresponding to the allocated resource.
1298
1401
  """
@@ -1300,7 +1403,7 @@ class InfrahubClient(BaseClient):
1300
1403
  raise ValueError("resource_pool is not an IP address pool")
1301
1404
 
1302
1405
  branch = branch or self.default_branch
1303
- mutation_name = "IPAddressPoolGetResource"
1406
+ mutation_name = "InfrahubIPAddressPoolGetResource"
1304
1407
 
1305
1408
  query = self._build_ip_address_allocation_query(
1306
1409
  resource_pool_id=resource_pool.id,
@@ -1367,7 +1470,7 @@ class InfrahubClient(BaseClient):
1367
1470
  branch: str | None = ...,
1368
1471
  timeout: int | None = ...,
1369
1472
  tracker: str | None = ...,
1370
- raise_for_error: bool = ...,
1473
+ raise_for_error: bool | None = ...,
1371
1474
  ) -> SchemaType: ...
1372
1475
 
1373
1476
  @overload
@@ -1415,7 +1518,7 @@ class InfrahubClient(BaseClient):
1415
1518
  branch: str | None = ...,
1416
1519
  timeout: int | None = ...,
1417
1520
  tracker: str | None = ...,
1418
- raise_for_error: bool = ...,
1521
+ raise_for_error: bool | None = ...,
1419
1522
  ) -> CoreNode | None: ...
1420
1523
 
1421
1524
  async def allocate_next_ip_prefix(
@@ -1430,7 +1533,7 @@ class InfrahubClient(BaseClient):
1430
1533
  branch: str | None = None,
1431
1534
  timeout: int | None = None,
1432
1535
  tracker: str | None = None,
1433
- raise_for_error: bool = True,
1536
+ raise_for_error: bool | None = None,
1434
1537
  ) -> CoreNode | SchemaType | None:
1435
1538
  """Allocate a new IP prefix by using the provided resource pool.
1436
1539
 
@@ -1444,7 +1547,7 @@ class InfrahubClient(BaseClient):
1444
1547
  branch: Name of the branch to allocate from. Defaults to default_branch.
1445
1548
  timeout: Flag to indicate whether to populate the store with the retrieved nodes.
1446
1549
  tracker: The offset for pagination.
1447
- raise_for_error: The limit for pagination.
1550
+ raise_for_error (bool, optional): Deprecated, raise an error if the HTTP status is not 2XX.
1448
1551
  Returns:
1449
1552
  InfrahubNode: Node corresponding to the allocated resource.
1450
1553
  """
@@ -1452,7 +1555,7 @@ class InfrahubClient(BaseClient):
1452
1555
  raise ValueError("resource_pool is not an IP prefix pool")
1453
1556
 
1454
1557
  branch = branch or self.default_branch
1455
- mutation_name = "IPPrefixPoolGetResource"
1558
+ mutation_name = "InfrahubIPPrefixPoolGetResource"
1456
1559
 
1457
1560
  query = self._build_ip_prefix_allocation_query(
1458
1561
  resource_pool_id=resource_pool.id,
@@ -1463,7 +1566,11 @@ class InfrahubClient(BaseClient):
1463
1566
  data=data,
1464
1567
  )
1465
1568
  response = await self.execute_graphql(
1466
- query=query.render(), branch_name=branch, timeout=timeout, tracker=tracker, raise_for_error=raise_for_error
1569
+ query=query.render(),
1570
+ branch_name=branch,
1571
+ timeout=timeout,
1572
+ tracker=tracker,
1573
+ raise_for_error=raise_for_error,
1467
1574
  )
1468
1575
 
1469
1576
  if response[mutation_name]["ok"]:
@@ -1472,10 +1579,15 @@ class InfrahubClient(BaseClient):
1472
1579
  return None
1473
1580
 
1474
1581
  async def create_batch(self, return_exceptions: bool = False) -> InfrahubBatch:
1475
- return InfrahubBatch(semaphore=self.concurrent_execution_limit, return_exceptions=return_exceptions)
1582
+ return InfrahubBatch(
1583
+ semaphore=self.concurrent_execution_limit,
1584
+ return_exceptions=return_exceptions,
1585
+ )
1476
1586
 
1477
1587
  async def get_list_repositories(
1478
- self, branches: dict[str, BranchData] | None = None, kind: str = "CoreGenericRepository"
1588
+ self,
1589
+ branches: dict[str, BranchData] | None = None,
1590
+ kind: str = "CoreGenericRepository",
1479
1591
  ) -> dict[str, RepositoryData]:
1480
1592
  branches = branches or await self.branch.all()
1481
1593
 
@@ -1513,7 +1625,11 @@ class InfrahubClient(BaseClient):
1513
1625
  return repositories
1514
1626
 
1515
1627
  async def repository_update_commit(
1516
- self, branch_name: str, repository_id: str, commit: str, is_read_only: bool = False
1628
+ self,
1629
+ branch_name: str,
1630
+ repository_id: str,
1631
+ commit: str,
1632
+ is_read_only: bool = False,
1517
1633
  ) -> bool:
1518
1634
  variables = {"repository_id": str(repository_id), "commit": str(commit)}
1519
1635
  await self.execute_graphql(
@@ -1539,6 +1655,38 @@ class InfrahubClient(BaseClient):
1539
1655
 
1540
1656
  self.mode = InfrahubClientMode.DEFAULT
1541
1657
 
1658
+ async def convert_object_type(
1659
+ self,
1660
+ node_id: str,
1661
+ target_kind: str,
1662
+ branch: str | None = None,
1663
+ fields_mapping: dict[str, ConversionFieldInput] | None = None,
1664
+ ) -> InfrahubNode:
1665
+ """
1666
+ Convert a given node to another kind on a given branch. `fields_mapping` keys are target fields names
1667
+ and its values indicate how to fill in these fields. Any mandatory field not having an equivalent field
1668
+ in the source kind should be specified in this mapping. See https://docs.infrahub.app/guides/object-convert-type
1669
+ for more information.
1670
+ """
1671
+
1672
+ if fields_mapping is None:
1673
+ mapping_dict = {}
1674
+ else:
1675
+ mapping_dict = {field_name: model.model_dump(mode="json") for field_name, model in fields_mapping.items()}
1676
+
1677
+ branch_name = branch or self.default_branch
1678
+ response = await self.execute_graphql(
1679
+ query=CONVERT_OBJECT_MUTATION,
1680
+ variables={
1681
+ "node_id": node_id,
1682
+ "fields_mapping": mapping_dict,
1683
+ "target_kind": target_kind,
1684
+ },
1685
+ branch_name=branch_name,
1686
+ raise_for_error=True,
1687
+ )
1688
+ return await InfrahubNode.from_graphql(client=self, branch=branch_name, data=response["ConvertObjectType"])
1689
+
1542
1690
 
1543
1691
  class InfrahubClientSync(BaseClient):
1544
1692
  schema: InfrahubSchemaSync
@@ -1560,13 +1708,11 @@ class InfrahubClientSync(BaseClient):
1560
1708
  def get_version(self) -> str:
1561
1709
  """Return the Infrahub version."""
1562
1710
  response = self.execute_graphql(query="query { InfrahubInfo { version }}")
1563
- version = response.get("InfrahubInfo", {}).get("version", "")
1564
- return version
1711
+ return response.get("InfrahubInfo", {}).get("version", "")
1565
1712
 
1566
1713
  def get_user(self) -> dict:
1567
1714
  """Return user information"""
1568
- user_info = self.execute_graphql(query=QUERY_USER)
1569
- return user_info
1715
+ return self.execute_graphql(query=QUERY_USER)
1570
1716
 
1571
1717
  def get_user_permissions(self) -> dict:
1572
1718
  """Return user permissions"""
@@ -1625,7 +1771,7 @@ class InfrahubClientSync(BaseClient):
1625
1771
  branch_name: str | None = None,
1626
1772
  at: str | Timestamp | None = None,
1627
1773
  timeout: int | None = None,
1628
- raise_for_error: bool = True,
1774
+ raise_for_error: bool | None = None,
1629
1775
  tracker: str | None = None,
1630
1776
  ) -> dict:
1631
1777
  """Execute a GraphQL query (or mutation).
@@ -1637,13 +1783,18 @@ class InfrahubClientSync(BaseClient):
1637
1783
  branch_name (str, optional): Name of the branch on which the query will be executed. Defaults to None.
1638
1784
  at (str, optional): Time when the query should be executed. Defaults to None.
1639
1785
  timeout (int, optional): Timeout in second for the query. Defaults to None.
1640
- raise_for_error (bool, optional): Flag to indicate that we need to raise an exception if the response has some errors. Defaults to True.
1786
+ raise_for_error (bool | None, optional): Deprecated. Controls only HTTP status handling.
1787
+ - None (default) or True: HTTP errors raise via `resp.raise_for_status()`.
1788
+ - False: HTTP errors are not automatically raised.
1789
+ GraphQL errors always raise `GraphQLError`. Defaults to None.
1790
+
1641
1791
  Raises:
1642
- GraphQLError: When an error occurs during the execution of the GraphQL query or mutation.
1792
+ GraphQLError: When the GraphQL response contains errors.
1643
1793
 
1644
1794
  Returns:
1645
- dict: The result of the GraphQL query or mutation.
1795
+ dict: The GraphQL data payload (`response["data"]`).
1646
1796
  """
1797
+ raise_for_error_deprecation_warning(value=raise_for_error)
1647
1798
 
1648
1799
  branch_name = branch_name or self.default_branch
1649
1800
  url = self._graphql_url(branch_name=branch_name, at=at)
@@ -1666,7 +1817,7 @@ class InfrahubClientSync(BaseClient):
1666
1817
  try:
1667
1818
  resp = self._post(url=url, payload=payload, headers=headers, timeout=timeout)
1668
1819
 
1669
- if raise_for_error:
1820
+ if raise_for_error in (None, True):
1670
1821
  resp.raise_for_status()
1671
1822
 
1672
1823
  retry = False
@@ -1831,6 +1982,7 @@ class InfrahubClientSync(BaseClient):
1831
1982
  schema_kind: str,
1832
1983
  branch: str,
1833
1984
  prefetch_relationships: bool,
1985
+ include: list[str] | None,
1834
1986
  timeout: int | None = None,
1835
1987
  ) -> ProcessRelationsNodeSync:
1836
1988
  """Processes InfrahubNodeSync and their Relationships from the GraphQL query response.
@@ -1855,8 +2007,13 @@ class InfrahubClientSync(BaseClient):
1855
2007
  node = InfrahubNodeSync.from_graphql(client=self, branch=branch, data=item, timeout=timeout)
1856
2008
  nodes.append(node)
1857
2009
 
1858
- if prefetch_relationships:
1859
- node._process_relationships(node_data=item, branch=branch, related_nodes=related_nodes, timeout=timeout)
2010
+ if prefetch_relationships or (include and any(rel in include for rel in node._relationships)):
2011
+ node._process_relationships(
2012
+ node_data=item,
2013
+ branch=branch,
2014
+ related_nodes=related_nodes,
2015
+ timeout=timeout,
2016
+ )
1860
2017
 
1861
2018
  return ProcessRelationsNodeSync(nodes=nodes, related_nodes=related_nodes)
1862
2019
 
@@ -1980,6 +2137,7 @@ class InfrahubClientSync(BaseClient):
1980
2137
  branch=branch,
1981
2138
  prefetch_relationships=prefetch_relationships,
1982
2139
  timeout=timeout,
2140
+ include=include,
1983
2141
  )
1984
2142
  return response, process_result
1985
2143
 
@@ -2217,11 +2375,14 @@ class InfrahubClientSync(BaseClient):
2217
2375
  batch to manipulate objects that depend on each others.
2218
2376
  """
2219
2377
  return InfrahubBatchSync(
2220
- max_concurrent_execution=self.max_concurrent_execution, return_exceptions=return_exceptions
2378
+ max_concurrent_execution=self.max_concurrent_execution,
2379
+ return_exceptions=return_exceptions,
2221
2380
  )
2222
2381
 
2223
2382
  def get_list_repositories(
2224
- self, branches: dict[str, BranchData] | None = None, kind: str = "CoreGenericRepository"
2383
+ self,
2384
+ branches: dict[str, BranchData] | None = None,
2385
+ kind: str = "CoreGenericRepository",
2225
2386
  ) -> dict[str, RepositoryData]:
2226
2387
  raise NotImplementedError(
2227
2388
  "This method is deprecated in the async client and won't be implemented in the sync client."
@@ -2238,17 +2399,19 @@ class InfrahubClientSync(BaseClient):
2238
2399
  at: str | None = None,
2239
2400
  timeout: int | None = None,
2240
2401
  tracker: str | None = None,
2241
- raise_for_error: bool = True,
2402
+ raise_for_error: bool | None = None,
2242
2403
  ) -> dict:
2404
+ raise_for_error_deprecation_warning(value=raise_for_error)
2405
+
2243
2406
  url = f"{self.address}/api/query/{name}"
2244
2407
  url_params = copy.deepcopy(params or {})
2408
+ url_params["branch"] = branch_name or self.default_branch
2409
+
2245
2410
  headers = copy.copy(self.headers or {})
2246
2411
 
2247
2412
  if self.insert_tracker and tracker:
2248
2413
  headers["X-Infrahub-Tracker"] = tracker
2249
2414
 
2250
- if branch_name:
2251
- url_params["branch"] = branch_name
2252
2415
  if at:
2253
2416
  url_params["at"] = at
2254
2417
  if subscribers:
@@ -2283,26 +2446,67 @@ class InfrahubClientSync(BaseClient):
2283
2446
  timeout=timeout or self.default_timeout,
2284
2447
  )
2285
2448
 
2286
- if raise_for_error:
2449
+ if raise_for_error in (None, True):
2287
2450
  resp.raise_for_status()
2288
2451
 
2289
2452
  return decode_json(response=resp)
2290
2453
 
2454
+ def create_diff(
2455
+ self,
2456
+ branch: str,
2457
+ name: str,
2458
+ from_time: datetime,
2459
+ to_time: datetime,
2460
+ wait_until_completion: bool = True,
2461
+ ) -> bool | str:
2462
+ if from_time > to_time:
2463
+ raise ValueError("from_time must be <= to_time")
2464
+ input_data = {
2465
+ "wait_until_completion": wait_until_completion,
2466
+ "data": {
2467
+ "name": name,
2468
+ "branch": branch,
2469
+ "from_time": from_time.isoformat(),
2470
+ "to_time": to_time.isoformat(),
2471
+ },
2472
+ }
2473
+
2474
+ mutation_query = MUTATION_QUERY_TASK if not wait_until_completion else {"ok": None}
2475
+ query = Mutation(mutation="DiffUpdate", input_data=input_data, query=mutation_query)
2476
+ response = self.execute_graphql(query=query.render(), tracker="mutation-diff-update")
2477
+
2478
+ if not wait_until_completion and "task" in response["DiffUpdate"]:
2479
+ return response["DiffUpdate"]["task"]["id"]
2480
+
2481
+ return response["DiffUpdate"]["ok"]
2482
+
2291
2483
  def get_diff_summary(
2292
2484
  self,
2293
2485
  branch: str,
2486
+ name: str | None = None,
2487
+ from_time: datetime | None = None,
2488
+ to_time: datetime | None = None,
2294
2489
  timeout: int | None = None,
2295
2490
  tracker: str | None = None,
2296
- raise_for_error: bool = True,
2491
+ raise_for_error: bool | None = None,
2297
2492
  ) -> list[NodeDiff]:
2298
2493
  query = get_diff_summary_query()
2494
+ input_data = {"branch_name": branch}
2495
+ if name:
2496
+ input_data["name"] = name
2497
+ if from_time and to_time and from_time > to_time:
2498
+ raise ValueError("from_time must be <= to_time")
2499
+ if from_time:
2500
+ input_data["from_time"] = from_time.isoformat()
2501
+ if to_time:
2502
+ input_data["to_time"] = to_time.isoformat()
2299
2503
  response = self.execute_graphql(
2300
2504
  query=query,
2301
2505
  branch_name=branch,
2302
2506
  timeout=timeout,
2303
2507
  tracker=tracker,
2304
2508
  raise_for_error=raise_for_error,
2305
- variables={"branch_name": branch},
2509
+ variables=input_data,
2306
2510
  )
2307
2511
 
2308
2512
  node_diffs: list[NodeDiff] = []
@@ -2358,7 +2562,7 @@ class InfrahubClientSync(BaseClient):
2358
2562
  branch: str | None = ...,
2359
2563
  timeout: int | None = ...,
2360
2564
  tracker: str | None = ...,
2361
- raise_for_error: bool = ...,
2565
+ raise_for_error: bool | None = ...,
2362
2566
  ) -> SchemaTypeSync: ...
2363
2567
 
2364
2568
  @overload
@@ -2403,7 +2607,7 @@ class InfrahubClientSync(BaseClient):
2403
2607
  branch: str | None = ...,
2404
2608
  timeout: int | None = ...,
2405
2609
  tracker: str | None = ...,
2406
- raise_for_error: bool = ...,
2610
+ raise_for_error: bool | None = ...,
2407
2611
  ) -> CoreNodeSync | None: ...
2408
2612
 
2409
2613
  def allocate_next_ip_address(
@@ -2417,7 +2621,7 @@ class InfrahubClientSync(BaseClient):
2417
2621
  branch: str | None = None,
2418
2622
  timeout: int | None = None,
2419
2623
  tracker: str | None = None,
2420
- raise_for_error: bool = True,
2624
+ raise_for_error: bool | None = None,
2421
2625
  ) -> CoreNodeSync | SchemaTypeSync | None:
2422
2626
  """Allocate a new IP address by using the provided resource pool.
2423
2627
 
@@ -2438,7 +2642,7 @@ class InfrahubClientSync(BaseClient):
2438
2642
  raise ValueError("resource_pool is not an IP address pool")
2439
2643
 
2440
2644
  branch = branch or self.default_branch
2441
- mutation_name = "IPAddressPoolGetResource"
2645
+ mutation_name = "InfrahubIPAddressPoolGetResource"
2442
2646
 
2443
2647
  query = self._build_ip_address_allocation_query(
2444
2648
  resource_pool_id=resource_pool.id,
@@ -2448,7 +2652,11 @@ class InfrahubClientSync(BaseClient):
2448
2652
  data=data,
2449
2653
  )
2450
2654
  response = self.execute_graphql(
2451
- query=query.render(), branch_name=branch, timeout=timeout, tracker=tracker, raise_for_error=raise_for_error
2655
+ query=query.render(),
2656
+ branch_name=branch,
2657
+ timeout=timeout,
2658
+ tracker=tracker,
2659
+ raise_for_error=raise_for_error,
2452
2660
  )
2453
2661
 
2454
2662
  if response[mutation_name]["ok"]:
@@ -2501,7 +2709,7 @@ class InfrahubClientSync(BaseClient):
2501
2709
  branch: str | None = ...,
2502
2710
  timeout: int | None = ...,
2503
2711
  tracker: str | None = ...,
2504
- raise_for_error: bool = ...,
2712
+ raise_for_error: bool | None = ...,
2505
2713
  ) -> SchemaTypeSync: ...
2506
2714
 
2507
2715
  @overload
@@ -2549,7 +2757,7 @@ class InfrahubClientSync(BaseClient):
2549
2757
  branch: str | None = ...,
2550
2758
  timeout: int | None = ...,
2551
2759
  tracker: str | None = ...,
2552
- raise_for_error: bool = ...,
2760
+ raise_for_error: bool | None = ...,
2553
2761
  ) -> CoreNodeSync | None: ...
2554
2762
 
2555
2763
  def allocate_next_ip_prefix(
@@ -2564,7 +2772,7 @@ class InfrahubClientSync(BaseClient):
2564
2772
  branch: str | None = None,
2565
2773
  timeout: int | None = None,
2566
2774
  tracker: str | None = None,
2567
- raise_for_error: bool = True,
2775
+ raise_for_error: bool | None = None,
2568
2776
  ) -> CoreNodeSync | SchemaTypeSync | None:
2569
2777
  """Allocate a new IP prefix by using the provided resource pool.
2570
2778
 
@@ -2586,7 +2794,7 @@ class InfrahubClientSync(BaseClient):
2586
2794
  raise ValueError("resource_pool is not an IP prefix pool")
2587
2795
 
2588
2796
  branch = branch or self.default_branch
2589
- mutation_name = "IPPrefixPoolGetResource"
2797
+ mutation_name = "InfrahubIPPrefixPoolGetResource"
2590
2798
 
2591
2799
  query = self._build_ip_prefix_allocation_query(
2592
2800
  resource_pool_id=resource_pool.id,
@@ -2597,7 +2805,11 @@ class InfrahubClientSync(BaseClient):
2597
2805
  data=data,
2598
2806
  )
2599
2807
  response = self.execute_graphql(
2600
- query=query.render(), branch_name=branch, timeout=timeout, tracker=tracker, raise_for_error=raise_for_error
2808
+ query=query.render(),
2809
+ branch_name=branch,
2810
+ timeout=timeout,
2811
+ tracker=tracker,
2812
+ raise_for_error=raise_for_error,
2601
2813
  )
2602
2814
 
2603
2815
  if response[mutation_name]["ok"]:
@@ -2606,7 +2818,11 @@ class InfrahubClientSync(BaseClient):
2606
2818
  return None
2607
2819
 
2608
2820
  def repository_update_commit(
2609
- self, branch_name: str, repository_id: str, commit: str, is_read_only: bool = False
2821
+ self,
2822
+ branch_name: str,
2823
+ repository_id: str,
2824
+ commit: str,
2825
+ is_read_only: bool = False,
2610
2826
  ) -> bool:
2611
2827
  raise NotImplementedError(
2612
2828
  "This method is deprecated in the async client and won't be implemented in the sync client."
@@ -2626,10 +2842,21 @@ class InfrahubClientSync(BaseClient):
2626
2842
  base_headers = copy.copy(self.headers or {})
2627
2843
  headers.update(base_headers)
2628
2844
 
2629
- return self._request(url=url, method=HTTPMethod.GET, headers=headers, timeout=timeout or self.default_timeout)
2845
+ return self._request(
2846
+ url=url,
2847
+ method=HTTPMethod.GET,
2848
+ headers=headers,
2849
+ timeout=timeout or self.default_timeout,
2850
+ )
2630
2851
 
2631
2852
  @handle_relogin_sync
2632
- def _post(self, url: str, payload: dict, headers: dict | None = None, timeout: int | None = None) -> httpx.Response:
2853
+ def _post(
2854
+ self,
2855
+ url: str,
2856
+ payload: dict,
2857
+ headers: dict | None = None,
2858
+ timeout: int | None = None,
2859
+ ) -> httpx.Response:
2633
2860
  """Execute a HTTP POST with HTTPX.
2634
2861
 
2635
2862
  Raises:
@@ -2643,24 +2870,39 @@ class InfrahubClientSync(BaseClient):
2643
2870
  headers.update(base_headers)
2644
2871
 
2645
2872
  return self._request(
2646
- url=url, method=HTTPMethod.POST, payload=payload, headers=headers, timeout=timeout or self.default_timeout
2873
+ url=url,
2874
+ method=HTTPMethod.POST,
2875
+ payload=payload,
2876
+ headers=headers,
2877
+ timeout=timeout or self.default_timeout,
2647
2878
  )
2648
2879
 
2649
2880
  def _request(
2650
- self, url: str, method: HTTPMethod, headers: dict[str, Any], timeout: int, payload: dict | None = None
2881
+ self,
2882
+ url: str,
2883
+ method: HTTPMethod,
2884
+ headers: dict[str, Any],
2885
+ timeout: int,
2886
+ payload: dict | None = None,
2651
2887
  ) -> httpx.Response:
2652
2888
  response = self._request_method(url=url, method=method, headers=headers, timeout=timeout, payload=payload)
2653
2889
  self._record(response)
2654
2890
  return response
2655
2891
 
2656
2892
  def _default_request_method(
2657
- self, url: str, method: HTTPMethod, headers: dict[str, Any], timeout: int, payload: dict | None = None
2893
+ self,
2894
+ url: str,
2895
+ method: HTTPMethod,
2896
+ headers: dict[str, Any],
2897
+ timeout: int,
2898
+ payload: dict | None = None,
2658
2899
  ) -> httpx.Response:
2659
2900
  params: dict[str, Any] = {}
2660
2901
  if payload:
2661
2902
  params["json"] = payload
2662
2903
 
2663
- proxy_config: dict[str, str | dict[str, httpx.HTTPTransport]] = {}
2904
+ proxy_config: ProxyConfigSync = {"proxy": None, "mounts": None}
2905
+
2664
2906
  if self.config.proxy:
2665
2907
  proxy_config["proxy"] = self.config.proxy
2666
2908
  elif self.config.proxy_mounts.is_set:
@@ -2670,7 +2912,7 @@ class InfrahubClientSync(BaseClient):
2670
2912
  }
2671
2913
 
2672
2914
  with httpx.Client(
2673
- **proxy_config, # type: ignore[arg-type]
2915
+ **proxy_config,
2674
2916
  verify=self.config.tls_ca_file if self.config.tls_ca_file else not self.config.tls_insecure,
2675
2917
  ) as client:
2676
2918
  try:
@@ -2696,7 +2938,10 @@ class InfrahubClientSync(BaseClient):
2696
2938
  response = self._request(
2697
2939
  url=url,
2698
2940
  method=HTTPMethod.POST,
2699
- headers={"content-type": "application/json", "Authorization": f"Bearer {self.refresh_token}"},
2941
+ headers={
2942
+ "content-type": "application/json",
2943
+ "Authorization": f"Bearer {self.refresh_token}",
2944
+ },
2700
2945
  timeout=self.default_timeout,
2701
2946
  )
2702
2947
 
@@ -2729,7 +2974,10 @@ class InfrahubClientSync(BaseClient):
2729
2974
  response = self._request(
2730
2975
  url=url,
2731
2976
  method=HTTPMethod.POST,
2732
- payload={"username": self.config.username, "password": self.config.password},
2977
+ payload={
2978
+ "username": self.config.username,
2979
+ "password": self.config.password,
2980
+ },
2733
2981
  headers={"content-type": "application/json"},
2734
2982
  timeout=self.default_timeout,
2735
2983
  )
@@ -2753,3 +3001,35 @@ class InfrahubClientSync(BaseClient):
2753
3001
  self.group_context.update_group()
2754
3002
 
2755
3003
  self.mode = InfrahubClientMode.DEFAULT
3004
+
3005
+ def convert_object_type(
3006
+ self,
3007
+ node_id: str,
3008
+ target_kind: str,
3009
+ branch: str | None = None,
3010
+ fields_mapping: dict[str, ConversionFieldInput] | None = None,
3011
+ ) -> InfrahubNodeSync:
3012
+ """
3013
+ Convert a given node to another kind on a given branch. `fields_mapping` keys are target fields names
3014
+ and its values indicate how to fill in these fields. Any mandatory field not having an equivalent field
3015
+ in the source kind should be specified in this mapping. See https://docs.infrahub.app/guides/object-convert-type
3016
+ for more information.
3017
+ """
3018
+
3019
+ if fields_mapping is None:
3020
+ mapping_dict = {}
3021
+ else:
3022
+ mapping_dict = {field_name: model.model_dump(mode="json") for field_name, model in fields_mapping.items()}
3023
+
3024
+ branch_name = branch or self.default_branch
3025
+ response = self.execute_graphql(
3026
+ query=CONVERT_OBJECT_MUTATION,
3027
+ variables={
3028
+ "node_id": node_id,
3029
+ "fields_mapping": mapping_dict,
3030
+ "target_kind": target_kind,
3031
+ },
3032
+ branch_name=branch_name,
3033
+ raise_for_error=True,
3034
+ )
3035
+ return InfrahubNodeSync.from_graphql(client=self, branch=branch_name, data=response["ConvertObjectType"])