infrahub-server 1.5.5__py3-none-any.whl → 1.6.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 (100) hide show
  1. infrahub/api/artifact.py +5 -3
  2. infrahub/auth.py +5 -6
  3. infrahub/cli/db.py +3 -3
  4. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +2 -2
  5. infrahub/cli/dev.py +30 -0
  6. infrahub/config.py +62 -14
  7. infrahub/constants/database.py +5 -5
  8. infrahub/core/branch/models.py +24 -6
  9. infrahub/core/diff/model/diff.py +2 -2
  10. infrahub/core/graph/constraints.py +2 -2
  11. infrahub/core/manager.py +155 -29
  12. infrahub/core/merge.py +29 -2
  13. infrahub/core/migrations/graph/m041_deleted_dup_edges.py +2 -3
  14. infrahub/core/migrations/shared.py +2 -2
  15. infrahub/core/node/__init__.py +1 -1
  16. infrahub/core/node/ipam.py +4 -4
  17. infrahub/core/node/node_property_attribute.py +2 -2
  18. infrahub/core/protocols.py +7 -1
  19. infrahub/core/query/branch.py +11 -0
  20. infrahub/core/query/standard_node.py +3 -0
  21. infrahub/core/relationship/model.py +3 -9
  22. infrahub/core/schema/__init__.py +3 -3
  23. infrahub/core/task/user_task.py +2 -2
  24. infrahub/core/validators/enum.py +2 -2
  25. infrahub/dependencies/interface.py +2 -2
  26. infrahub/events/constants.py +2 -2
  27. infrahub/git/base.py +43 -1
  28. infrahub/git/models.py +2 -1
  29. infrahub/git/repository.py +5 -1
  30. infrahub/git/tasks.py +28 -1
  31. infrahub/git/utils.py +9 -0
  32. infrahub/graphql/analyzer.py +4 -4
  33. infrahub/graphql/mutations/computed_attribute.py +1 -1
  34. infrahub/graphql/mutations/convert_object_type.py +1 -1
  35. infrahub/graphql/mutations/display_label.py +1 -1
  36. infrahub/graphql/mutations/hfid.py +1 -1
  37. infrahub/graphql/mutations/ipam.py +1 -1
  38. infrahub/graphql/mutations/profile.py +1 -0
  39. infrahub/graphql/mutations/relationship.py +2 -2
  40. infrahub/graphql/mutations/resource_manager.py +1 -1
  41. infrahub/graphql/queries/__init__.py +2 -1
  42. infrahub/graphql/queries/branch.py +58 -3
  43. infrahub/graphql/queries/ipam.py +9 -4
  44. infrahub/graphql/queries/resource_manager.py +5 -8
  45. infrahub/graphql/queries/search.py +3 -3
  46. infrahub/graphql/schema.py +2 -0
  47. infrahub/graphql/types/__init__.py +3 -1
  48. infrahub/graphql/types/branch.py +98 -2
  49. infrahub/lock.py +6 -6
  50. infrahub/patch/constants.py +2 -2
  51. infrahub/task_manager/task.py +2 -2
  52. infrahub/telemetry/constants.py +2 -2
  53. infrahub/trigger/models.py +2 -2
  54. infrahub/utils.py +1 -1
  55. infrahub/validators/tasks.py +1 -1
  56. infrahub/workers/infrahub_async.py +37 -0
  57. infrahub_sdk/async_typer.py +2 -1
  58. infrahub_sdk/batch.py +2 -2
  59. infrahub_sdk/client.py +8 -9
  60. infrahub_sdk/config.py +2 -2
  61. infrahub_sdk/ctl/branch.py +1 -1
  62. infrahub_sdk/ctl/cli.py +2 -2
  63. infrahub_sdk/ctl/cli_commands.py +2 -1
  64. infrahub_sdk/ctl/graphql.py +2 -2
  65. infrahub_sdk/ctl/importer.py +1 -1
  66. infrahub_sdk/ctl/utils.py +3 -3
  67. infrahub_sdk/node/attribute.py +11 -10
  68. infrahub_sdk/node/constants.py +1 -2
  69. infrahub_sdk/node/node.py +54 -11
  70. infrahub_sdk/node/related_node.py +1 -1
  71. infrahub_sdk/object_store.py +4 -4
  72. infrahub_sdk/operation.py +2 -2
  73. infrahub_sdk/protocols_generator/generator.py +1 -1
  74. infrahub_sdk/pytest_plugin/items/jinja2_transform.py +1 -1
  75. infrahub_sdk/pytest_plugin/models.py +1 -1
  76. infrahub_sdk/pytest_plugin/plugin.py +1 -1
  77. infrahub_sdk/query_groups.py +2 -2
  78. infrahub_sdk/schema/__init__.py +10 -11
  79. infrahub_sdk/schema/main.py +2 -2
  80. infrahub_sdk/schema/repository.py +2 -2
  81. infrahub_sdk/spec/object.py +2 -2
  82. infrahub_sdk/spec/range_expansion.py +1 -1
  83. infrahub_sdk/template/__init__.py +2 -1
  84. infrahub_sdk/transfer/importer/json.py +3 -3
  85. infrahub_sdk/types.py +2 -2
  86. infrahub_sdk/utils.py +2 -2
  87. {infrahub_server-1.5.5.dist-info → infrahub_server-1.6.0b0.dist-info}/METADATA +58 -59
  88. {infrahub_server-1.5.5.dist-info → infrahub_server-1.6.0b0.dist-info}/RECORD +217 -223
  89. {infrahub_server-1.5.5.dist-info → infrahub_server-1.6.0b0.dist-info}/WHEEL +1 -1
  90. infrahub_server-1.6.0b0.dist-info/entry_points.txt +12 -0
  91. infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
  92. infrahub_testcontainers/docker-compose.test.yml +1 -1
  93. infrahub/core/schema/generated/__init__.py +0 -0
  94. infrahub/core/schema/generated/attribute_schema.py +0 -133
  95. infrahub/core/schema/generated/base_node_schema.py +0 -111
  96. infrahub/core/schema/generated/genericnode_schema.py +0 -30
  97. infrahub/core/schema/generated/node_schema.py +0 -40
  98. infrahub/core/schema/generated/relationship_schema.py +0 -141
  99. infrahub_server-1.5.5.dist-info/entry_points.txt +0 -13
  100. {infrahub_server-1.5.5.dist-info → infrahub_server-1.6.0b0.dist-info/licenses}/LICENSE.txt +0 -0
@@ -2,11 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from graphene import Boolean, Field, Int, String
5
+ from graphene import Boolean, Field, Int, List, NonNull, String
6
6
 
7
7
  from infrahub.core.branch import Branch
8
8
  from infrahub.core.constants import GLOBAL_BRANCH_NAME
9
9
 
10
+ from ...exceptions import BranchNotFoundError
10
11
  from .enums import InfrahubBranchStatus
11
12
  from .standard_node import InfrahubObjectType
12
13
 
@@ -33,6 +34,10 @@ class BranchType(InfrahubObjectType):
33
34
  name = "Branch"
34
35
  model = Branch
35
36
 
37
+ @staticmethod
38
+ async def _map_fields_to_graphql(objs: list[Branch], fields: dict) -> list[dict[str, Any]]:
39
+ return [await obj.to_graphql(fields=fields) for obj in objs]
40
+
36
41
  @classmethod
37
42
  async def get_list(
38
43
  cls,
@@ -46,4 +51,95 @@ class BranchType(InfrahubObjectType):
46
51
  if not objs:
47
52
  return []
48
53
 
49
- return [await obj.to_graphql(fields=fields) for obj in objs if obj.name != GLOBAL_BRANCH_NAME]
54
+ return await cls._map_fields_to_graphql(objs=objs, fields=fields)
55
+
56
+ @classmethod
57
+ async def get_by_name(
58
+ cls,
59
+ fields: dict,
60
+ graphql_context: GraphqlContext,
61
+ name: str,
62
+ ) -> dict[str, Any]:
63
+ branch_responses = await cls.get_list(fields=fields, graphql_context=graphql_context, name=name)
64
+
65
+ if branch_responses:
66
+ return branch_responses[0]
67
+ raise BranchNotFoundError(f"Branch with name '{name}' not found")
68
+
69
+
70
+ class RequiredStringValueField(InfrahubObjectType):
71
+ value = String(required=True)
72
+
73
+
74
+ class NonRequiredStringValueField(InfrahubObjectType):
75
+ value = String(required=False)
76
+
77
+
78
+ class NonRequiredIntValueField(InfrahubObjectType):
79
+ value = Int(required=False)
80
+
81
+
82
+ class NonRequiredBooleanValueField(InfrahubObjectType):
83
+ value = Boolean(required=False)
84
+
85
+
86
+ class StatusField(InfrahubObjectType):
87
+ value = InfrahubBranchStatus(required=True)
88
+
89
+
90
+ class InfrahubBranch(BranchType):
91
+ name = Field(RequiredStringValueField, required=True)
92
+ description = Field(NonRequiredStringValueField, required=False)
93
+ origin_branch = Field(NonRequiredStringValueField, required=False)
94
+ branched_from = Field(NonRequiredStringValueField, required=False)
95
+ graph_version = Field(NonRequiredIntValueField, required=False)
96
+ status = Field(StatusField, required=True)
97
+ sync_with_git = Field(NonRequiredBooleanValueField, required=False)
98
+ is_default = Field(NonRequiredBooleanValueField, required=False)
99
+ is_isolated = Field(
100
+ NonRequiredBooleanValueField, required=False, deprecation_reason="non isolated mode is not supported anymore"
101
+ )
102
+ has_schema_changes = Field(NonRequiredBooleanValueField, required=False)
103
+
104
+ class Meta:
105
+ description = "InfrahubBranch"
106
+ name = "InfrahubBranch"
107
+
108
+ @staticmethod
109
+ async def _map_fields_to_graphql(objs: list[Branch], fields: dict) -> list[dict[str, Any]]:
110
+ field_keys = fields.keys()
111
+ result: list[dict[str, Any]] = []
112
+ for obj in objs:
113
+ if obj.name == GLOBAL_BRANCH_NAME:
114
+ continue
115
+ data: dict[str, Any] = {}
116
+ for field in field_keys:
117
+ if field == "id":
118
+ data["id"] = obj.uuid
119
+ continue
120
+ value = getattr(obj, field, None)
121
+ if isinstance(fields.get(field), dict):
122
+ data[field] = {"value": value}
123
+ else:
124
+ data[field] = value
125
+ result.append(data)
126
+ return result
127
+
128
+
129
+ class InfrahubBranchEdge(InfrahubObjectType):
130
+ node = Field(InfrahubBranch, required=True)
131
+
132
+
133
+ class InfrahubBranchType(InfrahubObjectType):
134
+ count = Field(Int, description="Total number of items")
135
+ edges = Field(NonNull(List(of_type=NonNull(InfrahubBranchEdge))))
136
+ default_branch = Field(
137
+ InfrahubBranch,
138
+ required=True,
139
+ description="The default branch of the Infrahub instance, provides a direct way to access the default branch regardless of filters.",
140
+ )
141
+
142
+ @classmethod
143
+ async def get_list_count(cls, graphql_context: GraphqlContext, **kwargs: Any) -> int:
144
+ async with graphql_context.db.start_session(read_only=True) as db:
145
+ return await Branch.get_list_count(db=db, **kwargs)
infrahub/lock.py CHANGED
@@ -58,7 +58,7 @@ class InfrahubMultiLock:
58
58
  self.locks = locks or []
59
59
  self.metrics = metrics
60
60
 
61
- async def __aenter__(self):
61
+ async def __aenter__(self) -> None:
62
62
  await self.acquire()
63
63
 
64
64
  async def __aexit__(
@@ -66,7 +66,7 @@ class InfrahubMultiLock:
66
66
  exc_type: type[BaseException] | None,
67
67
  exc_value: BaseException | None,
68
68
  traceback: TracebackType | None,
69
- ):
69
+ ) -> None:
70
70
  await self.release()
71
71
 
72
72
  async def acquire(self) -> None:
@@ -86,7 +86,7 @@ class NATSLock:
86
86
  self.token = None
87
87
  self.service = service
88
88
 
89
- async def __aenter__(self):
89
+ async def __aenter__(self) -> None:
90
90
  await self.acquire()
91
91
 
92
92
  async def __aexit__(
@@ -94,7 +94,7 @@ class NATSLock:
94
94
  exc_type: type[BaseException] | None,
95
95
  exc_value: BaseException | None,
96
96
  traceback: TracebackType | None,
97
- ):
97
+ ) -> None:
98
98
  await self.release()
99
99
 
100
100
  async def acquire(self) -> None:
@@ -162,7 +162,7 @@ class InfrahubLock:
162
162
  def acquire_time(self, value: int) -> None:
163
163
  self._acquire_time = value
164
164
 
165
- async def __aenter__(self):
165
+ async def __aenter__(self) -> None:
166
166
  await self.acquire()
167
167
 
168
168
  async def __aexit__(
@@ -170,7 +170,7 @@ class InfrahubLock:
170
170
  exc_type: type[BaseException] | None,
171
171
  exc_value: BaseException | None,
172
172
  traceback: TracebackType | None,
173
- ):
173
+ ) -> None:
174
174
  await self.release()
175
175
 
176
176
  async def acquire(self) -> None:
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
 
4
- class PatchPlanFilename(str, Enum):
4
+ class PatchPlanFilename(StrEnum):
5
5
  VERTICES_TO_ADD = "vertices_to_add.json"
6
6
  VERTICES_TO_UPDATE = "vertices_to_update.json"
7
7
  VERTICES_TO_DELETE = "vertices_to_delete.json"
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  import hashlib
3
3
  import json
4
- from datetime import datetime, timedelta, timezone
4
+ from datetime import UTC, datetime, timedelta
5
5
  from typing import Any
6
6
  from uuid import UUID
7
7
 
@@ -353,7 +353,7 @@ class PrefectTask:
353
353
  logger = get_logger()
354
354
 
355
355
  async with get_client(sync_client=False) as client:
356
- cutoff = datetime.now(timezone.utc) - timedelta(days=days_to_keep)
356
+ cutoff = datetime.now(UTC) - timedelta(days=days_to_keep)
357
357
 
358
358
  flow_run_filter = FlowRunFilter(
359
359
  start_time=FlowRunFilterStartTime(before_=cutoff), # type: ignore[arg-type]
@@ -1,9 +1,9 @@
1
- from enum import Enum
1
+ from enum import StrEnum
2
2
 
3
3
  TELEMETRY_KIND: str = "community"
4
4
  TELEMETRY_VERSION: str = "20250318"
5
5
 
6
6
 
7
- class InfrahubType(str, Enum):
7
+ class InfrahubType(StrEnum):
8
8
  COMMUNITY = "community"
9
9
  ENTERPRISE = "enterprise"
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from datetime import timedelta
4
- from enum import Enum, StrEnum
4
+ from enum import StrEnum
5
5
  from typing import TYPE_CHECKING, Any, TypeVar
6
6
 
7
7
  from prefect.events.actions import RunDeployment
@@ -92,7 +92,7 @@ class TriggerSetupReport(BaseModel):
92
92
  return created + updated
93
93
 
94
94
 
95
- class TriggerType(str, Enum):
95
+ class TriggerType(StrEnum):
96
96
  ACTION_TRIGGER_RULE = "action_trigger_rule"
97
97
  BUILTIN = "builtin"
98
98
  WEBHOOK = "webhook"
infrahub/utils.py CHANGED
@@ -76,7 +76,7 @@ def get_nested_dict(nested_dict: dict[str, Any], keys: list[str]) -> dict[str, A
76
76
  return current_level if isinstance(current_level, dict) else {}
77
77
 
78
78
 
79
- def get_all_subclasses(cls: AnyClass) -> list[AnyClass]:
79
+ def get_all_subclasses[AnyClass: type](cls: AnyClass) -> list[AnyClass]:
80
80
  """Recursively get all subclasses of the given class."""
81
81
  subclasses: list[AnyClass] = []
82
82
  for subclass in cls.__subclasses__():
@@ -12,7 +12,7 @@ from .events import send_start_validator
12
12
  ValidatorType = TypeVar("ValidatorType", bound=CoreValidator)
13
13
 
14
14
 
15
- async def start_validator(
15
+ async def start_validator[ValidatorType: CoreValidator](
16
16
  client: InfrahubClient,
17
17
  validator: CoreValidator | None,
18
18
  validator_type: type[ValidatorType],
@@ -1,5 +1,7 @@
1
+ import asyncio
1
2
  import logging
2
3
  import os
4
+ from pathlib import Path
3
5
  from typing import Any
4
6
 
5
7
  import typer
@@ -122,6 +124,7 @@ class InfrahubWorkerAsync(BaseWorker):
122
124
  )
123
125
 
124
126
  set_component_type(component_type=self.component_type)
127
+ await self.set_git_global_config()
125
128
  await self._init_services(client=client)
126
129
 
127
130
  if not registry.schema_has_been_initialized():
@@ -204,3 +207,37 @@ class InfrahubWorkerAsync(BaseWorker):
204
207
  )
205
208
 
206
209
  self.service = service
210
+
211
+ async def set_git_global_config(self) -> None:
212
+ global_config_file = config.SETTINGS.git.global_config_file
213
+ if not os.getenv("GIT_CONFIG_GLOBAL") and global_config_file:
214
+ config_dir = Path(global_config_file).parent
215
+ try:
216
+ config_dir.mkdir(exist_ok=True, parents=True)
217
+ except FileExistsError:
218
+ pass
219
+ os.environ["GIT_CONFIG_GLOBAL"] = global_config_file
220
+ self._logger.info(f"Set git config file to {global_config_file}")
221
+
222
+ await self._run_git_config_global(config.SETTINGS.git.user_name, setting_name="user.name")
223
+ await self._run_git_config_global(config.SETTINGS.git.user_email, setting_name="user.email")
224
+ await self._run_git_config_global("'*'", "--replace-all", setting_name="safe.directory")
225
+ await self._run_git_config_global("true", setting_name="credential.usehttppath")
226
+ await self._run_git_config_global(config.SETTINGS.dev.git_credential_helper, setting_name="credential.helper")
227
+
228
+ async def _run_git_config_global(self, *args: str, setting_name: str) -> None:
229
+ proc = await asyncio.create_subprocess_exec(
230
+ "git",
231
+ "config",
232
+ "--global",
233
+ setting_name,
234
+ *args,
235
+ stdout=asyncio.subprocess.PIPE,
236
+ stderr=asyncio.subprocess.PIPE,
237
+ )
238
+ _, stderr = await proc.communicate()
239
+ if proc.returncode != 0:
240
+ error_msg = stderr.decode("utf-8", errors="ignore").strip() or "unknown error"
241
+ self._logger.error(f"Failed to set git {setting_name}: %s", error_msg)
242
+ else:
243
+ self._logger.info(f"Git {setting_name} set")
@@ -2,8 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import inspect
5
+ from collections.abc import Callable
5
6
  from functools import partial, wraps
6
- from typing import Any, Callable
7
+ from typing import Any
7
8
 
8
9
  from typer import Typer
9
10
 
infrahub_sdk/batch.py CHANGED
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- from collections.abc import AsyncGenerator, Awaitable, Generator
4
+ from collections.abc import AsyncGenerator, Awaitable, Callable, Generator
5
5
  from concurrent.futures import ThreadPoolExecutor
6
6
  from dataclasses import dataclass
7
- from typing import TYPE_CHECKING, Any, Callable
7
+ from typing import TYPE_CHECKING, Any
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from .node import InfrahubNode, InfrahubNodeSync
infrahub_sdk/client.py CHANGED
@@ -5,14 +5,13 @@ import copy
5
5
  import logging
6
6
  import time
7
7
  import warnings
8
- from collections.abc import Coroutine, Mapping, MutableMapping
8
+ from collections.abc import Callable, Coroutine, Mapping, MutableMapping
9
9
  from datetime import datetime
10
10
  from functools import wraps
11
11
  from time import sleep
12
12
  from typing import (
13
13
  TYPE_CHECKING,
14
14
  Any,
15
- Callable,
16
15
  Literal,
17
16
  TypedDict,
18
17
  TypeVar,
@@ -300,7 +299,7 @@ class BaseClient:
300
299
  if prefix_length:
301
300
  input_data["prefix_length"] = prefix_length
302
301
  if member_type:
303
- if member_type not in ("prefix", "address"):
302
+ if member_type not in {"prefix", "address"}:
304
303
  raise ValueError("member_type possible values are 'prefix' or 'address'")
305
304
  input_data["member_type"] = member_type
306
305
  if prefix_type:
@@ -957,7 +956,7 @@ class InfrahubClient(BaseClient):
957
956
  try:
958
957
  resp = await self._post(url=url, payload=payload, headers=headers, timeout=timeout)
959
958
 
960
- if raise_for_error in (None, True):
959
+ if raise_for_error in {None, True}:
961
960
  resp.raise_for_status()
962
961
 
963
962
  retry = False
@@ -971,7 +970,7 @@ class InfrahubClient(BaseClient):
971
970
  self.log.error(f"Unable to connect to {self.address} .. ")
972
971
  raise
973
972
  except httpx.HTTPStatusError as exc:
974
- if exc.response.status_code in [401, 403]:
973
+ if exc.response.status_code in {401, 403}:
975
974
  response = decode_json(response=exc.response)
976
975
  errors = response.get("errors", [])
977
976
  messages = [error.get("message") for error in errors]
@@ -1209,7 +1208,7 @@ class InfrahubClient(BaseClient):
1209
1208
  timeout=timeout or self.default_timeout,
1210
1209
  )
1211
1210
 
1212
- if raise_for_error in (None, True):
1211
+ if raise_for_error in {None, True}:
1213
1212
  resp.raise_for_status()
1214
1213
 
1215
1214
  return decode_json(response=resp)
@@ -1818,7 +1817,7 @@ class InfrahubClientSync(BaseClient):
1818
1817
  try:
1819
1818
  resp = self._post(url=url, payload=payload, headers=headers, timeout=timeout)
1820
1819
 
1821
- if raise_for_error in (None, True):
1820
+ if raise_for_error in {None, True}:
1822
1821
  resp.raise_for_status()
1823
1822
 
1824
1823
  retry = False
@@ -1832,7 +1831,7 @@ class InfrahubClientSync(BaseClient):
1832
1831
  self.log.error(f"Unable to connect to {self.address} .. ")
1833
1832
  raise
1834
1833
  except httpx.HTTPStatusError as exc:
1835
- if exc.response.status_code in [401, 403]:
1834
+ if exc.response.status_code in {401, 403}:
1836
1835
  response = decode_json(response=exc.response)
1837
1836
  errors = response.get("errors", [])
1838
1837
  messages = [error.get("message") for error in errors]
@@ -2447,7 +2446,7 @@ class InfrahubClientSync(BaseClient):
2447
2446
  timeout=timeout or self.default_timeout,
2448
2447
  )
2449
2448
 
2450
- if raise_for_error in (None, True):
2449
+ if raise_for_error in {None, True}:
2451
2450
  resp.raise_for_status()
2452
2451
 
2453
2452
  return decode_json(response=resp)
infrahub_sdk/config.py CHANGED
@@ -173,7 +173,7 @@ class Config(ConfigBase):
173
173
  # When using structlog the logger doesn't expose the expected methods by looking at the
174
174
  # object to pydantic rejects them. This is a workaround to allow structlog to be used
175
175
  # as a logger
176
- return self.log # type: ignore
176
+ return self.log # type: ignore[return-value]
177
177
 
178
178
  @model_validator(mode="before")
179
179
  @classmethod
@@ -194,7 +194,7 @@ class Config(ConfigBase):
194
194
  "log": self.log,
195
195
  }
196
196
  covered_keys = list(config.keys())
197
- for field in Config.model_fields.keys():
197
+ for field in Config.model_fields:
198
198
  if field not in covered_keys:
199
199
  config[field] = deepcopy(getattr(self, field))
200
200
 
@@ -49,7 +49,7 @@ async def list_branch(_: str = CONFIG_PARAM) -> None:
49
49
  table.add_column("Status")
50
50
 
51
51
  # identify the default branch and always print it first
52
- default_branch = [branch for branch in branches.values() if branch.is_default][0]
52
+ default_branch = next(branch for branch in branches.values() if branch.is_default)
53
53
  table.add_row(
54
54
  default_branch.name,
55
55
  default_branch.description or " - ",
infrahub_sdk/ctl/cli.py CHANGED
@@ -4,8 +4,8 @@ try:
4
4
  from .cli_commands import app
5
5
  except ImportError as exc:
6
6
  sys.exit(
7
- f"Module {exc.name} is not available, install the 'ctl' extra of the infrahub-sdk package, `pip install 'infrahub-sdk[ctl]'` or enable the "
8
- "Poetry shell and run `poetry install --extras ctl`."
7
+ f"Module {exc.name} is not available, install the 'ctl' extra of the infrahub-sdk package, "
8
+ f"`pip install 'infrahub-sdk[ctl]'` or run `uv sync --extra ctl`."
9
9
  )
10
10
 
11
11
  __all__ = ["app"]
@@ -6,8 +6,9 @@ import importlib
6
6
  import logging
7
7
  import platform
8
8
  import sys
9
+ from collections.abc import Callable
9
10
  from pathlib import Path
10
- from typing import TYPE_CHECKING, Any, Callable, Optional
11
+ from typing import TYPE_CHECKING, Any, Optional
11
12
 
12
13
  import typer
13
14
  import ujson
@@ -108,7 +108,7 @@ async def export_schema(
108
108
  schema_text = await client.schema.get_graphql_schema()
109
109
 
110
110
  destination.parent.mkdir(parents=True, exist_ok=True)
111
- destination.write_text(schema_text)
111
+ destination.write_text(schema_text, encoding="utf-8")
112
112
  console.print(f"[green]Schema exported to {destination}")
113
113
 
114
114
 
@@ -180,5 +180,5 @@ async def generate_return_types(
180
180
 
181
181
  generate_result_types(directory=directory, package=package_generator, fragment=module_fragment)
182
182
 
183
- for file_name in package_generator._result_types_files.keys():
183
+ for file_name in package_generator._result_types_files:
184
184
  console.print(f"[green]Generated {file_name} in {directory}")
@@ -16,7 +16,7 @@ from .parameters import CONFIG_PARAM
16
16
 
17
17
  def local_directory() -> Path:
18
18
  # We use a function here to avoid failure when generating the documentation due to directory name
19
- return Path().resolve()
19
+ return Path.cwd()
20
20
 
21
21
 
22
22
  def load(
infrahub_sdk/ctl/utils.py CHANGED
@@ -3,10 +3,10 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import logging
5
5
  import traceback
6
- from collections.abc import Coroutine
6
+ from collections.abc import Callable, Coroutine
7
7
  from functools import wraps
8
8
  from pathlib import Path
9
- from typing import TYPE_CHECKING, Any, Callable, NoReturn, Optional, TypeVar
9
+ from typing import TYPE_CHECKING, Any, NoReturn, Optional, TypeVar
10
10
 
11
11
  import typer
12
12
  from click.exceptions import Exit
@@ -46,7 +46,7 @@ def init_logging(debug: bool = False) -> None:
46
46
 
47
47
  log_level = "DEBUG" if debug else "INFO"
48
48
  FORMAT = "%(message)s"
49
- logging.basicConfig(level=log_level, format=FORMAT, datefmt="[%X]", handlers=[RichHandler()])
49
+ logging.basicConfig(level=log_level, format=FORMAT, datefmt="[%X]", handlers=[RichHandler(show_path=debug)])
50
50
  logging.getLogger("infrahubctl")
51
51
 
52
52
 
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ipaddress
4
- from typing import TYPE_CHECKING, Any, Callable, get_args
4
+ from collections.abc import Callable
5
+ from typing import TYPE_CHECKING, Any, get_args
5
6
 
6
7
  from ..protocols_base import CoreNodeBase
7
8
  from ..uuidt import UUIDT
@@ -25,7 +26,7 @@ class Attribute:
25
26
  self.name = name
26
27
  self._schema = schema
27
28
 
28
- if not isinstance(data, dict) or "value" not in data.keys():
29
+ if not isinstance(data, dict) or "value" not in data:
29
30
  data = {"value": data}
30
31
 
31
32
  self._properties_flag = PROPERTIES_FLAG
@@ -34,12 +35,12 @@ class Attribute:
34
35
 
35
36
  self._read_only = ["updated_at", "is_inherited"]
36
37
 
37
- self.id: str | None = data.get("id", None)
38
+ self.id: str | None = data.get("id")
38
39
 
39
- self._value: Any | None = data.get("value", None)
40
+ self._value: Any | None = data.get("value")
40
41
  self.value_has_been_mutated = False
41
- self.is_default: bool | None = data.get("is_default", None)
42
- self.is_from_profile: bool | None = data.get("is_from_profile", None)
42
+ self.is_default: bool | None = data.get("is_default")
43
+ self.is_from_profile: bool | None = data.get("is_from_profile")
43
44
 
44
45
  if self._value:
45
46
  value_mapper: dict[str, Callable] = {
@@ -49,11 +50,11 @@ class Attribute:
49
50
  mapper = value_mapper.get(schema.kind, lambda value: value)
50
51
  self._value = mapper(data.get("value"))
51
52
 
52
- self.is_inherited: bool | None = data.get("is_inherited", None)
53
- self.updated_at: str | None = data.get("updated_at", None)
53
+ self.is_inherited: bool | None = data.get("is_inherited")
54
+ self.updated_at: str | None = data.get("updated_at")
54
55
 
55
- self.is_visible: bool | None = data.get("is_visible", None)
56
- self.is_protected: bool | None = data.get("is_protected", None)
56
+ self.is_visible: bool | None = data.get("is_visible")
57
+ self.is_protected: bool | None = data.get("is_protected")
57
58
 
58
59
  self.source: NodeProperty | None = None
59
60
  self.owner: NodeProperty | None = None
@@ -1,12 +1,11 @@
1
1
  import ipaddress
2
2
  import re
3
- from typing import Union
4
3
 
5
4
  PROPERTIES_FLAG = ["is_visible", "is_protected"]
6
5
  PROPERTIES_OBJECT = ["source", "owner"]
7
6
  SAFE_VALUE = re.compile(r"(^[\. /:a-zA-Z0-9_-]+$)|(^$)")
8
7
 
9
- IP_TYPES = Union[ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network]
8
+ IP_TYPES = ipaddress.IPv4Interface | ipaddress.IPv6Interface | ipaddress.IPv4Network | ipaddress.IPv6Network
10
9
 
11
10
  ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE = (
12
11
  "calling artifact_fetch is only supported for nodes that are Artifact Definition target"