infrahub-server 1.4.10__py3-none-any.whl → 1.5.0b1__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 (178) hide show
  1. infrahub/actions/tasks.py +208 -16
  2. infrahub/api/artifact.py +3 -0
  3. infrahub/api/diff/diff.py +1 -1
  4. infrahub/api/query.py +2 -0
  5. infrahub/api/schema.py +3 -0
  6. infrahub/auth.py +5 -5
  7. infrahub/cli/db.py +26 -2
  8. infrahub/cli/db_commands/clean_duplicate_schema_fields.py +212 -0
  9. infrahub/config.py +7 -2
  10. infrahub/core/attribute.py +25 -22
  11. infrahub/core/branch/models.py +2 -2
  12. infrahub/core/branch/needs_rebase_status.py +11 -0
  13. infrahub/core/branch/tasks.py +4 -3
  14. infrahub/core/changelog/models.py +4 -12
  15. infrahub/core/constants/__init__.py +1 -0
  16. infrahub/core/constants/infrahubkind.py +1 -0
  17. infrahub/core/convert_object_type/object_conversion.py +201 -0
  18. infrahub/core/convert_object_type/repository_conversion.py +89 -0
  19. infrahub/core/convert_object_type/schema_mapping.py +27 -3
  20. infrahub/core/diff/model/path.py +4 -0
  21. infrahub/core/diff/payload_builder.py +1 -1
  22. infrahub/core/diff/query/artifact.py +1 -1
  23. infrahub/core/graph/__init__.py +1 -1
  24. infrahub/core/initialization.py +2 -2
  25. infrahub/core/ipam/utilization.py +1 -1
  26. infrahub/core/manager.py +9 -84
  27. infrahub/core/migrations/graph/__init__.py +6 -0
  28. infrahub/core/migrations/graph/m040_profile_attrs_in_db.py +166 -0
  29. infrahub/core/migrations/graph/m041_create_hfid_display_label_in_db.py +97 -0
  30. infrahub/core/migrations/graph/m042_backfill_hfid_display_label_in_db.py +86 -0
  31. infrahub/core/migrations/schema/node_attribute_add.py +5 -2
  32. infrahub/core/migrations/shared.py +5 -6
  33. infrahub/core/node/__init__.py +165 -42
  34. infrahub/core/node/constraints/attribute_uniqueness.py +3 -1
  35. infrahub/core/node/create.py +67 -35
  36. infrahub/core/node/lock_utils.py +98 -0
  37. infrahub/core/node/node_property_attribute.py +230 -0
  38. infrahub/core/node/standard.py +1 -1
  39. infrahub/core/property.py +11 -0
  40. infrahub/core/protocols.py +8 -1
  41. infrahub/core/query/attribute.py +27 -15
  42. infrahub/core/query/node.py +61 -185
  43. infrahub/core/query/relationship.py +43 -26
  44. infrahub/core/query/subquery.py +0 -8
  45. infrahub/core/registry.py +2 -2
  46. infrahub/core/relationship/constraints/count.py +1 -1
  47. infrahub/core/relationship/model.py +60 -20
  48. infrahub/core/schema/attribute_schema.py +0 -2
  49. infrahub/core/schema/basenode_schema.py +42 -2
  50. infrahub/core/schema/definitions/core/__init__.py +2 -0
  51. infrahub/core/schema/definitions/core/generator.py +2 -0
  52. infrahub/core/schema/definitions/core/group.py +16 -2
  53. infrahub/core/schema/definitions/core/repository.py +7 -0
  54. infrahub/core/schema/definitions/internal.py +14 -1
  55. infrahub/core/schema/generated/base_node_schema.py +6 -1
  56. infrahub/core/schema/node_schema.py +5 -2
  57. infrahub/core/schema/relationship_schema.py +0 -1
  58. infrahub/core/schema/schema_branch.py +137 -2
  59. infrahub/core/schema/schema_branch_display.py +123 -0
  60. infrahub/core/schema/schema_branch_hfid.py +114 -0
  61. infrahub/core/validators/aggregated_checker.py +1 -1
  62. infrahub/core/validators/determiner.py +12 -1
  63. infrahub/core/validators/relationship/peer.py +1 -1
  64. infrahub/core/validators/tasks.py +1 -1
  65. infrahub/display_labels/__init__.py +0 -0
  66. infrahub/display_labels/gather.py +48 -0
  67. infrahub/display_labels/models.py +240 -0
  68. infrahub/display_labels/tasks.py +186 -0
  69. infrahub/display_labels/triggers.py +22 -0
  70. infrahub/events/group_action.py +1 -1
  71. infrahub/events/node_action.py +1 -1
  72. infrahub/generators/constants.py +7 -0
  73. infrahub/generators/models.py +38 -12
  74. infrahub/generators/tasks.py +34 -16
  75. infrahub/git/base.py +38 -1
  76. infrahub/git/integrator.py +22 -14
  77. infrahub/graphql/analyzer.py +1 -1
  78. infrahub/graphql/api/dependencies.py +2 -4
  79. infrahub/graphql/api/endpoints.py +2 -2
  80. infrahub/graphql/app.py +2 -4
  81. infrahub/graphql/initialization.py +2 -3
  82. infrahub/graphql/manager.py +212 -137
  83. infrahub/graphql/middleware.py +12 -0
  84. infrahub/graphql/mutations/branch.py +11 -0
  85. infrahub/graphql/mutations/computed_attribute.py +110 -3
  86. infrahub/graphql/mutations/convert_object_type.py +34 -13
  87. infrahub/graphql/mutations/display_label.py +111 -0
  88. infrahub/graphql/mutations/generator.py +25 -7
  89. infrahub/graphql/mutations/hfid.py +118 -0
  90. infrahub/graphql/mutations/ipam.py +21 -8
  91. infrahub/graphql/mutations/main.py +37 -153
  92. infrahub/graphql/mutations/profile.py +195 -0
  93. infrahub/graphql/mutations/proposed_change.py +2 -1
  94. infrahub/graphql/mutations/relationship.py +2 -2
  95. infrahub/graphql/mutations/repository.py +22 -83
  96. infrahub/graphql/mutations/resource_manager.py +2 -2
  97. infrahub/graphql/mutations/schema.py +5 -5
  98. infrahub/graphql/mutations/webhook.py +1 -1
  99. infrahub/graphql/queries/resource_manager.py +1 -1
  100. infrahub/graphql/registry.py +173 -0
  101. infrahub/graphql/resolvers/resolver.py +2 -0
  102. infrahub/graphql/schema.py +8 -1
  103. infrahub/groups/tasks.py +1 -1
  104. infrahub/hfid/__init__.py +0 -0
  105. infrahub/hfid/gather.py +48 -0
  106. infrahub/hfid/models.py +240 -0
  107. infrahub/hfid/tasks.py +185 -0
  108. infrahub/hfid/triggers.py +22 -0
  109. infrahub/lock.py +67 -30
  110. infrahub/locks/__init__.py +0 -0
  111. infrahub/locks/tasks.py +37 -0
  112. infrahub/middleware.py +26 -1
  113. infrahub/patch/plan_writer.py +2 -2
  114. infrahub/profiles/__init__.py +0 -0
  115. infrahub/profiles/node_applier.py +101 -0
  116. infrahub/profiles/queries/__init__.py +0 -0
  117. infrahub/profiles/queries/get_profile_data.py +99 -0
  118. infrahub/profiles/tasks.py +63 -0
  119. infrahub/proposed_change/tasks.py +10 -1
  120. infrahub/repositories/__init__.py +0 -0
  121. infrahub/repositories/create_repository.py +113 -0
  122. infrahub/server.py +16 -3
  123. infrahub/services/__init__.py +8 -5
  124. infrahub/tasks/registry.py +6 -4
  125. infrahub/trigger/catalogue.py +4 -0
  126. infrahub/trigger/models.py +2 -0
  127. infrahub/trigger/tasks.py +3 -0
  128. infrahub/webhook/models.py +1 -1
  129. infrahub/workflows/catalogue.py +110 -3
  130. infrahub/workflows/initialization.py +16 -0
  131. infrahub/workflows/models.py +17 -2
  132. infrahub_sdk/branch.py +5 -8
  133. infrahub_sdk/checks.py +1 -1
  134. infrahub_sdk/client.py +364 -84
  135. infrahub_sdk/convert_object_type.py +61 -0
  136. infrahub_sdk/ctl/check.py +2 -3
  137. infrahub_sdk/ctl/cli_commands.py +18 -12
  138. infrahub_sdk/ctl/config.py +8 -2
  139. infrahub_sdk/ctl/generator.py +6 -3
  140. infrahub_sdk/ctl/graphql.py +184 -0
  141. infrahub_sdk/ctl/repository.py +39 -1
  142. infrahub_sdk/ctl/schema.py +18 -3
  143. infrahub_sdk/ctl/utils.py +4 -0
  144. infrahub_sdk/ctl/validate.py +5 -3
  145. infrahub_sdk/diff.py +4 -5
  146. infrahub_sdk/exceptions.py +2 -0
  147. infrahub_sdk/generator.py +7 -1
  148. infrahub_sdk/graphql/__init__.py +12 -0
  149. infrahub_sdk/graphql/constants.py +1 -0
  150. infrahub_sdk/graphql/plugin.py +85 -0
  151. infrahub_sdk/graphql/query.py +77 -0
  152. infrahub_sdk/{graphql.py → graphql/renderers.py} +88 -75
  153. infrahub_sdk/graphql/utils.py +40 -0
  154. infrahub_sdk/node/attribute.py +2 -0
  155. infrahub_sdk/node/node.py +28 -20
  156. infrahub_sdk/playback.py +1 -2
  157. infrahub_sdk/protocols.py +54 -6
  158. infrahub_sdk/pytest_plugin/plugin.py +7 -4
  159. infrahub_sdk/pytest_plugin/utils.py +40 -0
  160. infrahub_sdk/repository.py +1 -2
  161. infrahub_sdk/schema/__init__.py +38 -0
  162. infrahub_sdk/schema/main.py +1 -0
  163. infrahub_sdk/schema/repository.py +8 -0
  164. infrahub_sdk/spec/object.py +120 -7
  165. infrahub_sdk/spec/range_expansion.py +118 -0
  166. infrahub_sdk/timestamp.py +18 -6
  167. infrahub_sdk/transforms.py +1 -1
  168. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/METADATA +9 -11
  169. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/RECORD +177 -134
  170. infrahub_testcontainers/container.py +1 -1
  171. infrahub_testcontainers/docker-compose-cluster.test.yml +1 -1
  172. infrahub_testcontainers/docker-compose.test.yml +1 -1
  173. infrahub_testcontainers/models.py +2 -2
  174. infrahub_testcontainers/performance_test.py +4 -4
  175. infrahub/core/convert_object_type/conversion.py +0 -134
  176. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/LICENSE.txt +0 -0
  177. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/WHEEL +0 -0
  178. {infrahub_server-1.4.10.dist-info → infrahub_server-1.5.0b1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,118 @@
1
+ import itertools
2
+ import re
3
+
4
+ MATCH_PATTERN = r"(\[[\w,-]*[-,][\w,-]*\])"
5
+
6
+
7
+ def _escape_brackets(s: str) -> str:
8
+ return s.replace("\\[", "__LBRACK__").replace("\\]", "__RBRACK__")
9
+
10
+
11
+ def _unescape_brackets(s: str) -> str:
12
+ return s.replace("__LBRACK__", "[").replace("__RBRACK__", "]")
13
+
14
+
15
+ def _char_range_expand(char_range_str: str) -> list[str]:
16
+ """Expands a string of numbers or single-character letters."""
17
+ expanded_values: list[str] = []
18
+ # Special case: if no dash and no comma, and multiple characters, error if not all alphanumeric
19
+ if "," not in char_range_str and "-" not in char_range_str and len(char_range_str) > 1:
20
+ if not char_range_str.isalnum():
21
+ raise ValueError(f"Invalid non-alphanumeric range: [{char_range_str}]")
22
+ return list(char_range_str)
23
+
24
+ for value in char_range_str.split(","):
25
+ if not value:
26
+ # Malformed: empty part in comma-separated list
27
+ return [f"[{char_range_str}]"]
28
+ if "-" in value:
29
+ start_char, end_char = value.split("-", 1)
30
+ if not start_char or not end_char:
31
+ expanded_values.append(f"[{char_range_str}]")
32
+ return expanded_values
33
+ # Check if it's a numeric range
34
+ if start_char.isdigit() and end_char.isdigit():
35
+ start_num = int(start_char)
36
+ end_num = int(end_char)
37
+ step = 1 if start_num <= end_num else -1
38
+ expanded_values.extend(str(i) for i in range(start_num, end_num + step, step))
39
+ # Check if it's an alphabetical range (single character)
40
+ elif len(start_char) == 1 and len(end_char) == 1 and start_char.isalpha() and end_char.isalpha():
41
+ start_ord = ord(start_char)
42
+ end_ord = ord(end_char)
43
+ step = 1 if start_ord <= end_ord else -1
44
+ is_upper = start_char.isupper()
45
+ for i in range(start_ord, end_ord + step, step):
46
+ char = chr(i)
47
+ expanded_values.append(char.upper() if is_upper else char)
48
+ else:
49
+ # Mixed or unsupported range type, append as-is
50
+ expanded_values.append(value)
51
+ else:
52
+ # If the value is a single character or valid alphanumeric string, append
53
+ if not value.isalnum():
54
+ raise ValueError(f"Invalid non-alphanumeric value: [{value}]")
55
+ expanded_values.append(value)
56
+ return expanded_values
57
+
58
+
59
+ def _extract_constants(pattern: str, re_compiled: re.Pattern) -> tuple[list[int], list[list[str]]]:
60
+ cartesian_list = []
61
+ interface_constant = [0]
62
+ for match in re_compiled.finditer(pattern):
63
+ interface_constant.append(match.start())
64
+ interface_constant.append(match.end())
65
+ cartesian_list.append(_char_range_expand(match.group()[1:-1]))
66
+ return interface_constant, cartesian_list
67
+
68
+
69
+ def _expand_interfaces(pattern: str, interface_constant: list[int], cartesian_list: list[list[str]]) -> list[str]:
70
+ def _pairwise(lst: list[int]) -> list[tuple[int, int]]:
71
+ it = iter(lst)
72
+ return list(zip(it, it))
73
+
74
+ if interface_constant[-1] < len(pattern):
75
+ interface_constant.append(len(pattern))
76
+ interface_constant_out = _pairwise(interface_constant)
77
+ expanded_interfaces = []
78
+ for element in itertools.product(*cartesian_list):
79
+ current_interface = ""
80
+ for count, item in enumerate(interface_constant_out):
81
+ current_interface += pattern[item[0] : item[1]]
82
+ if count < len(element):
83
+ current_interface += element[count]
84
+ expanded_interfaces.append(_unescape_brackets(current_interface))
85
+ return expanded_interfaces
86
+
87
+
88
+ def range_expansion(interface_pattern: str) -> list[str]:
89
+ """Expand string pattern into a list of strings, supporting both
90
+ number and single-character alphabet ranges. Heavily inspired by
91
+ Netutils interface_range_expansion but adapted to support letters.
92
+
93
+ Args:
94
+ interface_pattern: The string pattern that will be parsed to create the list of interfaces.
95
+
96
+ Returns:
97
+ Contains the expanded list of interfaces.
98
+
99
+ Examples:
100
+ >>> from infrahub_sdk.spec.range_expansion import range_expansion
101
+ >>> range_expansion("Device [A-C]")
102
+ ['Device A', 'Device B', 'Device C']
103
+ >>> range_expansion("FastEthernet[1-2]/0/[10-15]")
104
+ ['FastEthernet1/0/10', 'FastEthernet1/0/11', 'FastEthernet1/0/12',
105
+ 'FastEthernet1/0/13', 'FastEthernet1/0/14', 'FastEthernet1/0/15',
106
+ 'FastEthernet2/0/10', 'FastEthernet2/0/11', 'FastEthernet2/0/12',
107
+ 'FastEthernet2/0/13', 'FastEthernet2/0/14', 'FastEthernet2/0/15']
108
+ >>> range_expansion("GigabitEthernet[a-c]/0/1")
109
+ ['GigabitEtherneta/0/1', 'GigabitEthernetb/0/1', 'GigabitEthernetc/0/1']
110
+ >>> range_expansion("Eth[a,c,e]/0/1")
111
+ ['Etha/0/1', 'Ethc/0/1', 'Ethe/0/1']
112
+ """
113
+ pattern_escaped = _escape_brackets(interface_pattern)
114
+ re_compiled = re.compile(MATCH_PATTERN)
115
+ if not re_compiled.search(pattern_escaped):
116
+ return [_unescape_brackets(pattern_escaped)]
117
+ interface_constant, cartesian_list = _extract_constants(pattern_escaped, re_compiled)
118
+ return _expand_interfaces(pattern_escaped, interface_constant, cartesian_list)
infrahub_sdk/timestamp.py CHANGED
@@ -3,14 +3,22 @@ from __future__ import annotations
3
3
  import re
4
4
  import warnings
5
5
  from datetime import datetime, timezone
6
- from typing import Literal
6
+ from typing import Literal, TypedDict
7
7
 
8
+ from typing_extensions import NotRequired
8
9
  from whenever import Date, Instant, LocalDateTime, OffsetDateTime, Time, ZonedDateTime
9
10
 
10
11
  from .exceptions import TimestampFormatError
11
12
 
12
13
  UTC = timezone.utc # Required for older versions of Python
13
14
 
15
+
16
+ class SubstractParams(TypedDict):
17
+ seconds: NotRequired[float]
18
+ minutes: NotRequired[float]
19
+ hours: NotRequired[float]
20
+
21
+
14
22
  REGEX_MAPPING = {
15
23
  "seconds": r"(\d+)(s|sec|second|seconds)",
16
24
  "minutes": r"(\d+)(m|min|minute|minutes)",
@@ -43,8 +51,7 @@ class Timestamp:
43
51
  @classmethod
44
52
  def _parse_string(cls, value: str) -> ZonedDateTime:
45
53
  try:
46
- zoned_date = ZonedDateTime.parse_common_iso(value)
47
- return zoned_date
54
+ return ZonedDateTime.parse_common_iso(value)
48
55
  except ValueError:
49
56
  pass
50
57
 
@@ -73,14 +80,19 @@ class Timestamp:
73
80
  except ValueError:
74
81
  pass
75
82
 
76
- params: dict[str, float] = {}
83
+ params: SubstractParams = {}
77
84
  for key, regex in REGEX_MAPPING.items():
78
85
  match = re.search(regex, value)
79
86
  if match:
80
- params[key] = float(match.group(1))
87
+ if key == "seconds":
88
+ params["seconds"] = float(match.group(1))
89
+ elif key == "minutes":
90
+ params["minutes"] = float(match.group(1))
91
+ elif key == "hours":
92
+ params["hours"] = float(match.group(1))
81
93
 
82
94
  if params:
83
- return ZonedDateTime.now("UTC").subtract(**params) # type: ignore[call-overload]
95
+ return ZonedDateTime.now("UTC").subtract(**params)
84
96
 
85
97
  raise TimestampFormatError(f"Invalid time format for {value}")
86
98
 
@@ -17,7 +17,7 @@ INFRAHUB_TRANSFORM_VARIABLE_TO_IMPORT = "INFRAHUB_TRANSFORMS"
17
17
  class InfrahubTransform(InfrahubOperation):
18
18
  name: str | None = None
19
19
  query: str
20
- timeout: int = 10
20
+ timeout: int = 60
21
21
 
22
22
  def __init__(
23
23
  self,
@@ -1,22 +1,21 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: infrahub-server
3
- Version: 1.4.10
3
+ Version: 1.5.0b1
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
  License: Apache-2.0
6
6
  Author: OpsMill
7
7
  Author-email: info@opsmill.com
8
- Requires-Python: >=3.10,<3.13
8
+ Requires-Python: >=3.12,<3.13
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
12
  Classifier: Programming Language :: Python :: 3.12
15
13
  Requires-Dist: Jinja2 (>=3,<4)
16
14
  Requires-Dist: aio-pika (>=9.4,<9.5)
17
15
  Requires-Dist: aiodataloader (==0.4.0)
16
+ Requires-Dist: ariadne-codegen (==0.15.3)
18
17
  Requires-Dist: asgi-correlation-id (==4.2.0)
19
- Requires-Dist: authlib (==1.3.2)
18
+ Requires-Dist: authlib (==1.6.5)
20
19
  Requires-Dist: bcrypt (>=4.1,<4.2)
21
20
  Requires-Dist: boto3 (==1.34.129)
22
21
  Requires-Dist: copier (>=9.8.0,<10.0.0)
@@ -34,15 +33,14 @@ Requires-Dist: neo4j (>=5.28,<5.29)
34
33
  Requires-Dist: neo4j-rust-ext (>=5.28,<5.29)
35
34
  Requires-Dist: netaddr (==1.3.0)
36
35
  Requires-Dist: netutils (==1.12.0)
37
- Requires-Dist: numpy (>=1.24.2,<2.0.0) ; python_version >= "3.9" and python_version < "3.12"
38
- Requires-Dist: numpy (>=1.26.2,<2.0.0) ; python_version >= "3.12"
36
+ Requires-Dist: numpy (>=1.26.2,<2.0.0)
39
37
  Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (==1.28.1)
40
38
  Requires-Dist: opentelemetry-exporter-otlp-proto-http (==1.28.1)
41
39
  Requires-Dist: opentelemetry-instrumentation-aio-pika (==0.49b1)
42
40
  Requires-Dist: opentelemetry-instrumentation-fastapi (==0.49b1)
43
- Requires-Dist: prefect (==3.4.13)
44
- Requires-Dist: prefect-redis (==0.2.4)
45
- Requires-Dist: pyarrow (>=14,<15)
41
+ Requires-Dist: prefect (==3.4.23)
42
+ Requires-Dist: prefect-redis (==0.2.5)
43
+ Requires-Dist: pyarrow (>=14)
46
44
  Requires-Dist: pydantic (>=2.10,<2.11)
47
45
  Requires-Dist: pydantic-settings (>=2.8,<2.9)
48
46
  Requires-Dist: pyjwt (>=2.8,<2.9)
@@ -53,7 +51,7 @@ Requires-Dist: redis[hiredis] (>=6.0.0,<7.0.0)
53
51
  Requires-Dist: rich (>=13,<14)
54
52
  Requires-Dist: starlette-exporter (>=0.23,<0.24)
55
53
  Requires-Dist: structlog (==24.1.0)
56
- Requires-Dist: toml (>=0.10,<0.11)
54
+ Requires-Dist: tomli (>=1.1.0) ; python_version < "3.11"
57
55
  Requires-Dist: typer (==0.12.5)
58
56
  Requires-Dist: ujson (>=5,<6)
59
57
  Requires-Dist: uvicorn[standard] (>=0.32,<0.33)