infrahub-server 1.1.7__py3-none-any.whl → 1.1.8__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 (66) hide show
  1. infrahub/core/diff/enricher/cardinality_one.py +5 -0
  2. infrahub/core/diff/enricher/hierarchy.py +17 -4
  3. infrahub/core/diff/enricher/labels.py +5 -0
  4. infrahub/core/diff/enricher/path_identifier.py +5 -0
  5. infrahub/core/diff/model/path.py +24 -1
  6. infrahub/core/diff/parent_node_adder.py +78 -0
  7. infrahub/core/diff/payload_builder.py +13 -2
  8. infrahub/core/diff/query/save.py +188 -182
  9. infrahub/core/diff/query/summary_counts_enricher.py +51 -4
  10. infrahub/core/diff/repository/deserializer.py +8 -3
  11. infrahub/core/diff/repository/repository.py +156 -38
  12. infrahub/core/diff/tasks.py +4 -4
  13. infrahub/core/graph/__init__.py +1 -1
  14. infrahub/core/graph/index.py +3 -0
  15. infrahub/core/migrations/graph/__init__.py +4 -0
  16. infrahub/core/migrations/graph/m019_restore_rels_to_time.py +256 -0
  17. infrahub/core/migrations/graph/m020_duplicate_edges.py +160 -0
  18. infrahub/core/migrations/query/node_duplicate.py +38 -18
  19. infrahub/core/migrations/schema/node_remove.py +26 -12
  20. infrahub/core/migrations/shared.py +10 -8
  21. infrahub/core/node/__init__.py +13 -8
  22. infrahub/core/node/constraints/grouped_uniqueness.py +16 -3
  23. infrahub/core/query/attribute.py +2 -0
  24. infrahub/core/query/node.py +66 -19
  25. infrahub/core/query/relationship.py +105 -16
  26. infrahub/core/query/resource_manager.py +2 -0
  27. infrahub/core/relationship/model.py +8 -12
  28. infrahub/core/schema/definitions/core.py +1 -0
  29. infrahub/database/__init__.py +1 -0
  30. infrahub/dependencies/builder/diff/deserializer.py +3 -1
  31. infrahub/dependencies/builder/diff/enricher/hierarchy.py +3 -1
  32. infrahub/dependencies/builder/diff/parent_node_adder.py +8 -0
  33. infrahub/graphql/mutations/diff.py +17 -10
  34. infrahub/graphql/mutations/resource_manager.py +3 -3
  35. infrahub_sdk/batch.py +2 -2
  36. infrahub_sdk/client.py +10 -2
  37. infrahub_sdk/config.py +4 -1
  38. infrahub_sdk/ctl/check.py +4 -4
  39. infrahub_sdk/ctl/cli_commands.py +16 -11
  40. infrahub_sdk/ctl/exceptions.py +0 -6
  41. infrahub_sdk/ctl/exporter.py +1 -1
  42. infrahub_sdk/ctl/generator.py +5 -5
  43. infrahub_sdk/ctl/importer.py +3 -2
  44. infrahub_sdk/ctl/menu.py +1 -1
  45. infrahub_sdk/ctl/object.py +1 -1
  46. infrahub_sdk/ctl/repository.py +23 -15
  47. infrahub_sdk/ctl/schema.py +2 -2
  48. infrahub_sdk/ctl/utils.py +6 -5
  49. infrahub_sdk/ctl/validate.py +2 -1
  50. infrahub_sdk/data.py +1 -1
  51. infrahub_sdk/exceptions.py +12 -0
  52. infrahub_sdk/generator.py +3 -0
  53. infrahub_sdk/node.py +8 -8
  54. infrahub_sdk/protocols.py +0 -1
  55. infrahub_sdk/schema/__init__.py +0 -3
  56. infrahub_sdk/testing/docker.py +30 -0
  57. infrahub_sdk/testing/schemas/animal.py +9 -0
  58. infrahub_sdk/transfer/exporter/json.py +1 -1
  59. infrahub_sdk/utils.py +11 -1
  60. infrahub_sdk/yaml.py +2 -3
  61. {infrahub_server-1.1.7.dist-info → infrahub_server-1.1.8.dist-info}/METADATA +1 -1
  62. {infrahub_server-1.1.7.dist-info → infrahub_server-1.1.8.dist-info}/RECORD +65 -62
  63. infrahub_sdk/ctl/_file.py +0 -13
  64. {infrahub_server-1.1.7.dist-info → infrahub_server-1.1.8.dist-info}/LICENSE.txt +0 -0
  65. {infrahub_server-1.1.7.dist-info → infrahub_server-1.1.8.dist-info}/WHEEL +0 -0
  66. {infrahub_server-1.1.7.dist-info → infrahub_server-1.1.8.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Optional
5
5
 
6
6
  import typer
7
7
  from rich.console import Console
@@ -9,7 +9,7 @@ from rich.console import Console
9
9
  from ..ctl import config
10
10
  from ..ctl.client import initialize_client
11
11
  from ..ctl.repository import get_repository_config
12
- from ..ctl.utils import execute_graphql_query, parse_cli_vars
12
+ from ..ctl.utils import execute_graphql_query, init_logging, parse_cli_vars
13
13
  from ..exceptions import ModuleImportError
14
14
  from ..node import InfrahubNode
15
15
 
@@ -20,11 +20,12 @@ if TYPE_CHECKING:
20
20
  async def run(
21
21
  generator_name: str,
22
22
  path: str, # noqa: ARG001
23
- debug: bool, # noqa: ARG001
23
+ debug: bool,
24
24
  list_available: bool,
25
25
  branch: str | None = None,
26
- variables: list[str] | None = None,
26
+ variables: Optional[list[str]] = None,
27
27
  ) -> None:
28
+ init_logging(debug=debug)
28
29
  repository_config = get_repository_config(Path(config.INFRAHUB_REPO_CONFIG_FILE))
29
30
 
30
31
  if list_available or not generator_name:
@@ -34,7 +35,6 @@ async def run(
34
35
  generator_config = repository_config.get_generator_definition(name=generator_name)
35
36
 
36
37
  console = Console()
37
-
38
38
  relative_path = str(generator_config.file_path.parent) if generator_config.file_path.parent != Path() else None
39
39
 
40
40
  try:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from asyncio import run as aiorun
4
4
  from pathlib import Path
5
+ from typing import Optional
5
6
 
6
7
  import typer
7
8
  from rich.console import Console
@@ -25,8 +26,8 @@ def load(
25
26
  ),
26
27
  quiet: bool = typer.Option(False, help="No console output"),
27
28
  _: str = CONFIG_PARAM,
28
- branch: str = typer.Option("main", help="Branch from which to export"),
29
- concurrent: int | None = typer.Option(
29
+ branch: str = typer.Option(None, help="Branch from which to export"),
30
+ concurrent: Optional[int] = typer.Option(
30
31
  None,
31
32
  help="Maximum number of requests to execute at the same time.",
32
33
  envvar="INFRAHUB_MAX_CONCURRENT_EXECUTION",
infrahub_sdk/ctl/menu.py CHANGED
@@ -27,7 +27,7 @@ def callback() -> None:
27
27
  async def load(
28
28
  menus: list[Path],
29
29
  debug: bool = False,
30
- branch: str = typer.Option("main", help="Branch on which to load the menu."),
30
+ branch: str = typer.Option(None, help="Branch on which to load the menu."),
31
31
  _: str = CONFIG_PARAM,
32
32
  ) -> None:
33
33
  """Load one or multiple menu files into Infrahub."""
@@ -27,7 +27,7 @@ def callback() -> None:
27
27
  async def load(
28
28
  paths: list[Path],
29
29
  debug: bool = False,
30
- branch: str = typer.Option("main", help="Branch on which to load the objects."),
30
+ branch: str = typer.Option(None, help="Branch on which to load the objects."),
31
31
  _: str = CONFIG_PARAM,
32
32
  ) -> None:
33
33
  """Load one or multiple objects files into Infrahub."""
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Optional
4
5
 
5
6
  import typer
6
7
  import yaml
@@ -8,15 +9,14 @@ from pydantic import ValidationError
8
9
  from rich.console import Console
9
10
  from rich.table import Table
10
11
 
11
- from infrahub_sdk.ctl.client import initialize_client
12
-
13
12
  from ..async_typer import AsyncTyper
14
- from ..ctl.exceptions import FileNotValidError
15
- from ..ctl.utils import init_logging
13
+ from ..exceptions import FileNotValidError
16
14
  from ..graphql import Mutation, Query
17
15
  from ..schema.repository import InfrahubRepositoryConfig
18
- from ._file import read_file
16
+ from ..utils import read_file
17
+ from .client import initialize_client
19
18
  from .parameters import CONFIG_PARAM
19
+ from .utils import init_logging
20
20
 
21
21
  app = AsyncTyper()
22
22
  console = Console()
@@ -69,12 +69,11 @@ async def add(
69
69
  name: str,
70
70
  location: str,
71
71
  description: str = "",
72
- username: str | None = None,
72
+ username: Optional[str] = None,
73
73
  password: str = "",
74
- commit: str = "",
74
+ ref: str = "",
75
75
  read_only: bool = False,
76
76
  debug: bool = False,
77
- branch: str = typer.Option("main", help="Branch on which to add the repository."),
78
77
  _: str = CONFIG_PARAM,
79
78
  ) -> None:
80
79
  """Add a new repository."""
@@ -86,15 +85,24 @@ async def add(
86
85
  "name": {"value": name},
87
86
  "location": {"value": location},
88
87
  "description": {"value": description},
89
- "commit": {"value": commit},
90
88
  },
91
89
  }
90
+ if read_only:
91
+ input_data["data"]["ref"] = {"value": ref}
92
+ else:
93
+ input_data["data"]["default_branch"] = {"value": ref}
92
94
 
93
95
  client = initialize_client()
94
96
 
95
- credential = await client.create(kind="CorePasswordCredential", name=name, username=username, password=password)
96
- await credential.save(allow_upsert=True)
97
- input_data["data"]["credential"] = {"id": credential.id}
97
+ if username or password:
98
+ credential = await client.create(
99
+ kind="CorePasswordCredential",
100
+ name=name,
101
+ username=username,
102
+ password=password,
103
+ )
104
+ await credential.save(allow_upsert=True)
105
+ input_data["data"]["credential"] = {"id": credential.id}
98
106
 
99
107
  query = Mutation(
100
108
  mutation="CoreReadOnlyRepositoryCreate" if read_only else "CoreRepositoryCreate",
@@ -102,18 +110,18 @@ async def add(
102
110
  query={"ok": None},
103
111
  )
104
112
 
105
- await client.execute_graphql(query=query.render(), branch_name=branch, tracker="mutation-repository-create")
113
+ await client.execute_graphql(query=query.render(), tracker="mutation-repository-create")
106
114
 
107
115
 
108
116
  @app.command()
109
117
  async def list(
110
- branch: str | None = None,
118
+ branch: Optional[str] = typer.Option(None, help="Branch on which to list repositories."),
111
119
  debug: bool = False,
112
120
  _: str = CONFIG_PARAM,
113
121
  ) -> None:
114
122
  init_logging(debug=debug)
115
123
 
116
- client = initialize_client(branch=branch)
124
+ client = initialize_client()
117
125
 
118
126
  repo_status_query = {
119
127
  "CoreGenericRepository": {
@@ -108,7 +108,7 @@ def get_node(schemas_data: list[dict], schema_index: int, node_index: int) -> di
108
108
  async def load(
109
109
  schemas: list[Path],
110
110
  debug: bool = False,
111
- branch: str = typer.Option("main", help="Branch on which to load the schema."),
111
+ branch: str = typer.Option(None, help="Branch on which to load the schema."),
112
112
  wait: int = typer.Option(0, help="Time in seconds to wait until the schema has converged across all workers"),
113
113
  _: str = CONFIG_PARAM,
114
114
  ) -> None:
@@ -159,7 +159,7 @@ async def load(
159
159
  async def check(
160
160
  schemas: list[Path],
161
161
  debug: bool = False,
162
- branch: str = typer.Option("main", help="Branch on which to check the schema."),
162
+ branch: str = typer.Option(None, help="Branch on which to check the schema."),
163
163
  _: str = CONFIG_PARAM,
164
164
  ) -> None:
165
165
  """Check if schema files are valid and what would be the impact of loading them with Infrahub."""
infrahub_sdk/ctl/utils.py CHANGED
@@ -6,7 +6,7 @@ import traceback
6
6
  from collections.abc import Coroutine
7
7
  from functools import wraps
8
8
  from pathlib import Path
9
- from typing import TYPE_CHECKING, Any, Callable, NoReturn, TypeVar
9
+ from typing import TYPE_CHECKING, Any, Callable, NoReturn, Optional, TypeVar
10
10
 
11
11
  import pendulum
12
12
  import typer
@@ -17,10 +17,10 @@ from rich.console import Console
17
17
  from rich.logging import RichHandler
18
18
  from rich.markup import escape
19
19
 
20
- from ..ctl.exceptions import FileNotValidError, QueryNotFoundError
21
20
  from ..exceptions import (
22
21
  AuthenticationError,
23
22
  Error,
23
+ FileNotValidError,
24
24
  GraphQLError,
25
25
  NodeNotFoundError,
26
26
  ResourceNotDefinedError,
@@ -30,6 +30,7 @@ from ..exceptions import (
30
30
  )
31
31
  from ..yaml import YamlFile
32
32
  from .client import initialize_client_sync
33
+ from .exceptions import QueryNotFoundError
33
34
 
34
35
  if TYPE_CHECKING:
35
36
  from ..schema.repository import InfrahubRepositoryConfig
@@ -88,7 +89,7 @@ def catch_exception(
88
89
  async def async_wrapper(*args: Any, **kwargs: Any) -> T:
89
90
  try:
90
91
  return await func(*args, **kwargs)
91
- except (Error, Exception) as exc: # pylint: disable=broad-exception-caught
92
+ except (Error, Exception) as exc:
92
93
  return handle_exception(exc=exc, console=console, exit_code=exit_code)
93
94
 
94
95
  return async_wrapper
@@ -97,7 +98,7 @@ def catch_exception(
97
98
  def wrapper(*args: Any, **kwargs: Any) -> T:
98
99
  try:
99
100
  return func(*args, **kwargs)
100
- except (Error, Exception) as exc: # pylint: disable=broad-exception-caught
101
+ except (Error, Exception) as exc:
101
102
  return handle_exception(exc=exc, console=console, exit_code=exit_code)
102
103
 
103
104
  return wrapper
@@ -144,7 +145,7 @@ def print_graphql_errors(console: Console, errors: list) -> None:
144
145
  console.print(f"[red]{escape(str(error))}")
145
146
 
146
147
 
147
- def parse_cli_vars(variables: list[str] | None) -> dict[str, str]:
148
+ def parse_cli_vars(variables: Optional[list[str]]) -> dict[str, str]:
148
149
  if not variables:
149
150
  return {}
150
151
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import sys
4
4
  from pathlib import Path
5
+ from typing import Optional
5
6
 
6
7
  import typer
7
8
  import ujson
@@ -57,7 +58,7 @@ async def validate_schema(schema: Path, _: str = CONFIG_PARAM) -> None:
57
58
  @catch_exception(console=console)
58
59
  def validate_graphql(
59
60
  query: str,
60
- variables: list[str] | None = typer.Argument(
61
+ variables: Optional[list[str]] = typer.Argument(
61
62
  None, help="Variables to pass along with the query. Format key=value key=value."
62
63
  ),
63
64
  debug: bool = typer.Option(False, help="Display more troubleshooting information."),
infrahub_sdk/data.py CHANGED
@@ -20,7 +20,7 @@ class RepositoryData(BaseModel):
20
20
  branch_info: dict[str, RepositoryBranchInfo] = Field(default_factory=dict)
21
21
 
22
22
  def get_staging_branch(self) -> str | None:
23
- for branch, info in self.branch_info.items(): # pylint: disable=no-member
23
+ for branch, info in self.branch_info.items():
24
24
  if info.internal_status == "staging":
25
25
  return branch
26
26
  return None
@@ -121,6 +121,12 @@ class AuthenticationError(Error):
121
121
  super().__init__(self.message)
122
122
 
123
123
 
124
+ class URLNotFoundError(Error):
125
+ def __init__(self, url: str):
126
+ self.message = f"`{url}` not found."
127
+ super().__init__(self.message)
128
+
129
+
124
130
  class FeatureNotSupportedError(Error):
125
131
  """Raised when trying to use a method on a node that doesn't support it."""
126
132
 
@@ -131,3 +137,9 @@ class UninitializedError(Error):
131
137
 
132
138
  class InvalidResponseError(Error):
133
139
  """Raised when an object requires an initialization step before use"""
140
+
141
+
142
+ class FileNotValidError(Error):
143
+ def __init__(self, name: str, message: str = ""):
144
+ self.message = message or f"Cannot parse '{name}' content."
145
+ super().__init__(self.message)
infrahub_sdk/generator.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import os
4
5
  from abc import abstractmethod
5
6
  from typing import TYPE_CHECKING
@@ -27,6 +28,7 @@ class InfrahubGenerator:
27
28
  generator_instance: str = "",
28
29
  params: dict | None = None,
29
30
  convert_query_response: bool = False,
31
+ logger: logging.Logger | None = None,
30
32
  ) -> None:
31
33
  self.query = query
32
34
  self.branch = branch
@@ -41,6 +43,7 @@ class InfrahubGenerator:
41
43
  self._related_nodes: list[InfrahubNode] = []
42
44
  self.infrahub_node = infrahub_node
43
45
  self.convert_query_response = convert_query_response
46
+ self.logger = logger if logger else logging.getLogger("infrahub.tasks")
44
47
 
45
48
  @property
46
49
  def store(self) -> NodeStore:
infrahub_sdk/node.py CHANGED
@@ -25,7 +25,6 @@ if TYPE_CHECKING:
25
25
  from .schema import AttributeSchemaAPI, MainSchemaTypesAPI, RelationshipSchemaAPI
26
26
  from .types import Order
27
27
 
28
- # pylint: disable=too-many-lines
29
28
 
30
29
  PROPERTIES_FLAG = ["is_visible", "is_protected"]
31
30
  PROPERTIES_OBJECT = ["source", "owner"]
@@ -188,6 +187,7 @@ class RelatedNodeBase:
188
187
  if node_data:
189
188
  self._id = node_data.get("id", None)
190
189
  self._hfid = node_data.get("hfid", None)
190
+ self._kind = node_data.get("kind", None)
191
191
  self._display_label = node_data.get("display_label", None)
192
192
  self._typename = node_data.get("__typename", None)
193
193
 
@@ -256,6 +256,8 @@ class RelatedNodeBase:
256
256
  data["id"] = self.id
257
257
  elif self.hfid is not None:
258
258
  data["hfid"] = self.hfid
259
+ if self._kind is not None:
260
+ data["kind"] = self._kind
259
261
 
260
262
  for prop_name in self._properties:
261
263
  if getattr(self, prop_name) is not None:
@@ -801,7 +803,7 @@ class InfrahubNodeBase:
801
803
  Returns:
802
804
  dict[str, Dict]: Representation of an input data in dict format
803
805
  """
804
- # pylint: disable=too-many-branches
806
+
805
807
  data = {}
806
808
  variables = {}
807
809
 
@@ -1119,14 +1121,14 @@ class InfrahubNode(InfrahubNodeBase):
1119
1121
  async def artifact_generate(self, name: str) -> None:
1120
1122
  self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
1121
1123
 
1122
- artifact = await self._client.get(kind="CoreArtifact", definition__name__value=name, object__ids=[self.id])
1124
+ artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
1123
1125
  await artifact.definition.fetch() # type: ignore[attr-defined]
1124
1126
  await artifact.definition.peer.generate([artifact.id]) # type: ignore[attr-defined]
1125
1127
 
1126
1128
  async def artifact_fetch(self, name: str) -> str | dict[str, Any]:
1127
1129
  self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
1128
1130
 
1129
- artifact = await self._client.get(kind="CoreArtifact", definition__name__value=name, object__ids=[self.id])
1131
+ artifact = await self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
1130
1132
  content = await self._client.object_store.get(identifier=artifact.storage_id.value) # type: ignore[attr-defined]
1131
1133
  return content
1132
1134
 
@@ -1251,7 +1253,6 @@ class InfrahubNode(InfrahubNodeBase):
1251
1253
  Returns:
1252
1254
  dict[str, Union[Any, Dict]]: GraphQL query in dictionary format
1253
1255
  """
1254
- # pylint: disable=too-many-branches
1255
1256
 
1256
1257
  data: dict[str, Any] = {}
1257
1258
 
@@ -1637,13 +1638,13 @@ class InfrahubNodeSync(InfrahubNodeBase):
1637
1638
 
1638
1639
  def artifact_generate(self, name: str) -> None:
1639
1640
  self._validate_artifact_support(ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE)
1640
- artifact = self._client.get(kind="CoreArtifact", definition__name__value=name, object__ids=[self.id])
1641
+ artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
1641
1642
  artifact.definition.fetch() # type: ignore[attr-defined]
1642
1643
  artifact.definition.peer.generate([artifact.id]) # type: ignore[attr-defined]
1643
1644
 
1644
1645
  def artifact_fetch(self, name: str) -> str | dict[str, Any]:
1645
1646
  self._validate_artifact_support(ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE)
1646
- artifact = self._client.get(kind="CoreArtifact", definition__name__value=name, object__ids=[self.id])
1647
+ artifact = self._client.get(kind="CoreArtifact", name__value=name, object__ids=[self.id])
1647
1648
  content = self._client.object_store.get(identifier=artifact.storage_id.value) # type: ignore[attr-defined]
1648
1649
  return content
1649
1650
 
@@ -1763,7 +1764,6 @@ class InfrahubNodeSync(InfrahubNodeBase):
1763
1764
  Returns:
1764
1765
  dict[str, Union[Any, Dict]]: GraphQL query in dictionary format
1765
1766
  """
1766
- # pylint: disable=too-many-branches
1767
1767
 
1768
1768
  data: dict[str, Any] = {}
1769
1769
 
infrahub_sdk/protocols.py CHANGED
@@ -29,7 +29,6 @@ if TYPE_CHECKING:
29
29
  StringOptional,
30
30
  )
31
31
 
32
- # pylint: disable=too-many-ancestors
33
32
 
34
33
  # ---------------------------------------------
35
34
  # ASYNC
@@ -61,9 +61,6 @@ __all__ = [
61
61
  ]
62
62
 
63
63
 
64
- # pylint: disable=redefined-builtin
65
-
66
-
67
64
  class DropdownMutationOptionalArgs(TypedDict):
68
65
  color: str | None
69
66
  description: str | None
@@ -1,10 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
1
5
  import pytest
2
6
  from infrahub_testcontainers.helpers import TestInfrahubDocker
7
+ from packaging.version import InvalidVersion, Version
3
8
 
4
9
  from .. import Config, InfrahubClient, InfrahubClientSync
5
10
 
11
+ INFRAHUB_VERSION = os.getenv("INFRAHUB_TESTING_IMAGE_VER", "latest")
12
+
13
+
14
+ def skip_version(min_infrahub_version: str | None = None, max_infrahub_version: str | None = None) -> bool:
15
+ """
16
+ Check if a test should be skipped depending on infrahub version.
17
+ """
18
+ try:
19
+ version = Version(INFRAHUB_VERSION)
20
+ except InvalidVersion:
21
+ # We would typically end up here for development purpose while running a CI test against
22
+ # unreleased versions of infrahub, like `stable` or `develop` branch.
23
+ # For now, we consider this means we are testing against the most recent version of infrahub,
24
+ # so we skip if the test should not be ran against a maximum version.
25
+ return max_infrahub_version is None
26
+
27
+ if min_infrahub_version is not None and version < Version(min_infrahub_version):
28
+ return True
29
+
30
+ return max_infrahub_version is not None and version > Version(max_infrahub_version)
31
+
6
32
 
7
33
  class TestInfrahubDockerClient(TestInfrahubDocker):
34
+ @pytest.fixture(scope="class")
35
+ def infrahub_version(self) -> str:
36
+ return INFRAHUB_VERSION
37
+
8
38
  @pytest.fixture(scope="class")
9
39
  def client(self, infrahub_port: int) -> InfrahubClient:
10
40
  return InfrahubClient(
@@ -80,6 +80,7 @@ class SchemaAnimal:
80
80
  namespace=NAMESPACE,
81
81
  include_in_menu=True,
82
82
  inherit_from=[TESTING_ANIMAL],
83
+ human_friendly_id=["owner__name__value", "name__value", "color__value"],
83
84
  display_labels=["name__value", "breed__value", "color__value"],
84
85
  order_by=["name__value"],
85
86
  attributes=[
@@ -108,6 +109,14 @@ class SchemaAnimal:
108
109
  identifier="person__animal",
109
110
  cardinality="many",
110
111
  direction=RelationshipDirection.INBOUND,
112
+ max_count=10,
113
+ ),
114
+ Rel(
115
+ name="favorite_animal",
116
+ peer=TESTING_ANIMAL,
117
+ identifier="favorite_animal",
118
+ cardinality="one",
119
+ direction=RelationshipDirection.INBOUND,
111
120
  ),
112
121
  Rel(
113
122
  name="best_friends",
@@ -98,7 +98,7 @@ class LineDelimitedJSONExporter(ExporterInterface):
98
98
  return many_relationships
99
99
 
100
100
  # FIXME: Split in smaller functions
101
- async def export( # pylint: disable=too-many-branches
101
+ async def export(
102
102
  self, export_directory: Path, namespaces: list[str], branch: str, exclude: list[str] | None = None
103
103
  ) -> None:
104
104
  illegal_namespaces = set(ILLEGAL_NAMESPACES)
infrahub_sdk/utils.py CHANGED
@@ -17,7 +17,7 @@ from graphql import (
17
17
 
18
18
  from infrahub_sdk.repository import GitRepoManager
19
19
 
20
- from .exceptions import JsonDecodeError
20
+ from .exceptions import FileNotValidError, JsonDecodeError
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from graphql import GraphQLResolveInfo
@@ -342,6 +342,16 @@ def write_to_file(path: Path, value: Any) -> bool:
342
342
  return written is not None
343
343
 
344
344
 
345
+ def read_file(file_name: Path) -> str:
346
+ if not file_name.is_file():
347
+ raise FileNotValidError(name=str(file_name), message=f"{file_name} is not a valid file")
348
+ try:
349
+ with Path.open(file_name, encoding="utf-8") as fobj:
350
+ return fobj.read()
351
+ except UnicodeDecodeError as exc:
352
+ raise FileNotValidError(name=str(file_name), message=f"Unable to read {file_name} with utf-8 encoding") from exc
353
+
354
+
345
355
  def get_user_permissions(data: list[dict]) -> dict:
346
356
  groups = {}
347
357
  for group in data:
infrahub_sdk/yaml.py CHANGED
@@ -8,9 +8,8 @@ import yaml
8
8
  from pydantic import BaseModel, Field
9
9
  from typing_extensions import Self
10
10
 
11
- from .ctl._file import read_file
12
- from .ctl.exceptions import FileNotValidError
13
- from .utils import find_files
11
+ from .exceptions import FileNotValidError
12
+ from .utils import find_files, read_file
14
13
 
15
14
 
16
15
  class InfrahubFileApiVersion(str, Enum):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: infrahub-server
3
- Version: 1.1.7
3
+ Version: 1.1.8
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