infrahub-server 1.2.1__py3-none-any.whl → 1.2.3__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 (41) hide show
  1. infrahub/computed_attribute/tasks.py +71 -67
  2. infrahub/config.py +3 -0
  3. infrahub/core/graph/__init__.py +1 -1
  4. infrahub/core/migrations/graph/__init__.py +4 -1
  5. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +69 -0
  6. infrahub/core/models.py +6 -0
  7. infrahub/core/node/__init__.py +4 -4
  8. infrahub/core/node/constraints/grouped_uniqueness.py +24 -9
  9. infrahub/core/query/ipam.py +1 -1
  10. infrahub/core/query/node.py +16 -5
  11. infrahub/core/schema/schema_branch.py +14 -5
  12. infrahub/exceptions.py +30 -2
  13. infrahub/git/base.py +80 -29
  14. infrahub/git/integrator.py +9 -31
  15. infrahub/menu/repository.py +6 -6
  16. infrahub/trigger/tasks.py +19 -18
  17. infrahub/workflows/utils.py +5 -5
  18. infrahub_sdk/client.py +6 -6
  19. infrahub_sdk/ctl/cli_commands.py +32 -37
  20. infrahub_sdk/ctl/render.py +39 -0
  21. infrahub_sdk/exceptions.py +6 -2
  22. infrahub_sdk/generator.py +1 -1
  23. infrahub_sdk/node.py +41 -12
  24. infrahub_sdk/protocols_base.py +8 -1
  25. infrahub_sdk/pytest_plugin/items/jinja2_transform.py +22 -26
  26. infrahub_sdk/store.py +351 -75
  27. infrahub_sdk/template/__init__.py +209 -0
  28. infrahub_sdk/template/exceptions.py +38 -0
  29. infrahub_sdk/template/filters.py +151 -0
  30. infrahub_sdk/template/models.py +10 -0
  31. infrahub_sdk/utils.py +7 -0
  32. {infrahub_server-1.2.1.dist-info → infrahub_server-1.2.3.dist-info}/METADATA +2 -1
  33. {infrahub_server-1.2.1.dist-info → infrahub_server-1.2.3.dist-info}/RECORD +39 -36
  34. infrahub_testcontainers/container.py +2 -0
  35. infrahub_testcontainers/docker-compose.test.yml +1 -0
  36. infrahub_testcontainers/haproxy.cfg +3 -3
  37. infrahub/support/__init__.py +0 -0
  38. infrahub/support/macro.py +0 -69
  39. {infrahub_server-1.2.1.dist-info → infrahub_server-1.2.3.dist-info}/LICENSE.txt +0 -0
  40. {infrahub_server-1.2.1.dist-info → infrahub_server-1.2.3.dist-info}/WHEEL +0 -0
  41. {infrahub_server-1.2.1.dist-info → infrahub_server-1.2.3.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,209 @@
1
+ from __future__ import annotations
2
+
3
+ import linecache
4
+ from pathlib import Path
5
+ from typing import Any, Callable, NoReturn
6
+
7
+ import jinja2
8
+ from jinja2 import meta, nodes
9
+ from jinja2.sandbox import SandboxedEnvironment
10
+ from netutils.utils import jinja2_convenience_function
11
+ from rich.syntax import Syntax
12
+ from rich.traceback import Traceback
13
+
14
+ from .exceptions import (
15
+ JinjaTemplateError,
16
+ JinjaTemplateNotFoundError,
17
+ JinjaTemplateOperationViolationError,
18
+ JinjaTemplateSyntaxError,
19
+ JinjaTemplateUndefinedError,
20
+ )
21
+ from .filters import AVAILABLE_FILTERS
22
+ from .models import UndefinedJinja2Error
23
+
24
+ netutils_filters = jinja2_convenience_function()
25
+
26
+
27
+ class Jinja2Template:
28
+ def __init__(
29
+ self,
30
+ template: str | Path,
31
+ template_directory: Path | None = None,
32
+ filters: dict[str, Callable] | None = None,
33
+ ) -> None:
34
+ self.is_string_based = isinstance(template, str)
35
+ self.is_file_based = isinstance(template, Path)
36
+ self._template = str(template)
37
+ self._template_directory = template_directory
38
+ self._environment: jinja2.Environment | None = None
39
+
40
+ self._available_filters = [filter_definition.name for filter_definition in AVAILABLE_FILTERS]
41
+ self._trusted_filters = [
42
+ filter_definition.name for filter_definition in AVAILABLE_FILTERS if filter_definition.trusted
43
+ ]
44
+
45
+ self._filters = filters or {}
46
+ for user_filter in self._filters:
47
+ self._available_filters.append(user_filter)
48
+ self._trusted_filters.append(user_filter)
49
+
50
+ self._template_definition: jinja2.Template | None = None
51
+
52
+ def get_environment(self) -> jinja2.Environment:
53
+ if self._environment:
54
+ return self._environment
55
+
56
+ if self.is_string_based:
57
+ return self._get_string_based_environment()
58
+
59
+ return self._get_file_based_environment()
60
+
61
+ def get_template(self) -> jinja2.Template:
62
+ if self._template_definition:
63
+ return self._template_definition
64
+
65
+ try:
66
+ if self.is_string_based:
67
+ template = self._get_string_based_template()
68
+ else:
69
+ template = self._get_file_based_template()
70
+ except jinja2.TemplateSyntaxError as exc:
71
+ self._raise_template_syntax_error(error=exc)
72
+ except jinja2.TemplateNotFound as exc:
73
+ raise JinjaTemplateNotFoundError(message=exc.message, filename=str(exc.name))
74
+
75
+ return template
76
+
77
+ def get_variables(self) -> list[str]:
78
+ env = self.get_environment()
79
+
80
+ template_source = self._template
81
+ if self.is_file_based and env.loader:
82
+ template_source = env.loader.get_source(env, self._template)[0]
83
+
84
+ template = env.parse(template_source)
85
+
86
+ return sorted(meta.find_undeclared_variables(template))
87
+
88
+ def validate(self, restricted: bool = True) -> None:
89
+ allowed_list = self._available_filters
90
+ if restricted:
91
+ allowed_list = self._trusted_filters
92
+
93
+ env = self.get_environment()
94
+ template_source = self._template
95
+ if self.is_file_based and env.loader:
96
+ template_source = env.loader.get_source(env, self._template)[0]
97
+
98
+ template = env.parse(template_source)
99
+ for node in template.find_all(nodes.Filter):
100
+ if node.name not in allowed_list:
101
+ raise JinjaTemplateOperationViolationError(f"The '{node.name}' filter isn't allowed to be used")
102
+
103
+ forbidden_operations = ["Call", "Import", "Include"]
104
+ if self.is_string_based and any(node.__class__.__name__ in forbidden_operations for node in template.body):
105
+ raise JinjaTemplateOperationViolationError(
106
+ f"These operations are forbidden for string based templates: {forbidden_operations}"
107
+ )
108
+
109
+ async def render(self, variables: dict[str, Any]) -> str:
110
+ template = self.get_template()
111
+ try:
112
+ output = await template.render_async(variables)
113
+ except jinja2.exceptions.TemplateNotFound as exc:
114
+ raise JinjaTemplateNotFoundError(message=exc.message, filename=str(exc.name), base_template=template.name)
115
+ except jinja2.TemplateSyntaxError as exc:
116
+ self._raise_template_syntax_error(error=exc)
117
+ except jinja2.UndefinedError as exc:
118
+ traceback = Traceback(show_locals=False)
119
+ errors = _identify_faulty_jinja_code(traceback=traceback)
120
+ raise JinjaTemplateUndefinedError(message=exc.message, errors=errors)
121
+ except Exception as exc:
122
+ if error_message := getattr(exc, "message", None):
123
+ message = error_message
124
+ else:
125
+ message = str(exc)
126
+ raise JinjaTemplateError(message=message or "Unknown template error")
127
+
128
+ return output
129
+
130
+ def _get_string_based_environment(self) -> jinja2.Environment:
131
+ env = SandboxedEnvironment(enable_async=True, undefined=jinja2.StrictUndefined)
132
+ self._set_filters(env=env)
133
+ self._environment = env
134
+ return self._environment
135
+
136
+ def _get_file_based_environment(self) -> jinja2.Environment:
137
+ template_loader = jinja2.FileSystemLoader(searchpath=str(self._template_directory))
138
+ env = jinja2.Environment(
139
+ loader=template_loader,
140
+ trim_blocks=True,
141
+ lstrip_blocks=True,
142
+ enable_async=True,
143
+ )
144
+ self._set_filters(env=env)
145
+ self._environment = env
146
+ return self._environment
147
+
148
+ def _set_filters(self, env: jinja2.Environment) -> None:
149
+ for default_filter in list(env.filters.keys()):
150
+ if default_filter not in self._available_filters:
151
+ del env.filters[default_filter]
152
+
153
+ # Add filters from netutils
154
+ env.filters.update(
155
+ {name: jinja_filter for name, jinja_filter in netutils_filters.items() if name in self._available_filters}
156
+ )
157
+ # Add user supplied filters
158
+ env.filters.update(self._filters)
159
+
160
+ def _get_string_based_template(self) -> jinja2.Template:
161
+ env = self.get_environment()
162
+ self._template_definition = env.from_string(self._template)
163
+ return self._template_definition
164
+
165
+ def _get_file_based_template(self) -> jinja2.Template:
166
+ env = self.get_environment()
167
+ self._template_definition = env.get_template(self._template)
168
+ return self._template_definition
169
+
170
+ def _raise_template_syntax_error(self, error: jinja2.TemplateSyntaxError) -> NoReturn:
171
+ filename: str | None = None
172
+ if error.filename and self._template_directory:
173
+ filename = error.filename
174
+ if error.filename.startswith(str(self._template_directory)):
175
+ filename = error.filename[len(str(self._template_directory)) :]
176
+
177
+ raise JinjaTemplateSyntaxError(message=error.message, filename=filename, lineno=error.lineno)
178
+
179
+
180
+ def _identify_faulty_jinja_code(traceback: Traceback, nbr_context_lines: int = 3) -> list[UndefinedJinja2Error]:
181
+ """This function identifies the faulty Jinja2 code and beautify it to provide meaningful information to the user.
182
+
183
+ We use the rich's Traceback to parse the complete stack trace and extract Frames for each exception found in the trace.
184
+ """
185
+ response = []
186
+
187
+ # Extract only the Jinja related exception
188
+ for frame in [frame for frame in traceback.trace.stacks[0].frames if not frame.filename.endswith(".py")]:
189
+ code = "".join(linecache.getlines(frame.filename))
190
+ if frame.filename == "<template>":
191
+ lexer_name = "text"
192
+ else:
193
+ lexer_name = Traceback._guess_lexer(frame.filename, code)
194
+ syntax = Syntax(
195
+ code,
196
+ lexer_name,
197
+ line_numbers=True,
198
+ line_range=(
199
+ frame.lineno - nbr_context_lines,
200
+ frame.lineno + nbr_context_lines,
201
+ ),
202
+ highlight_lines={frame.lineno},
203
+ code_width=88,
204
+ theme=traceback.theme,
205
+ dedent=False,
206
+ )
207
+ response.append(UndefinedJinja2Error(frame=frame, syntax=syntax))
208
+
209
+ return response
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from infrahub_sdk.exceptions import Error
6
+
7
+ if TYPE_CHECKING:
8
+ from .models import UndefinedJinja2Error
9
+
10
+
11
+ class JinjaTemplateError(Error):
12
+ def __init__(self, message: str) -> None:
13
+ self.message = message
14
+
15
+
16
+ class JinjaTemplateNotFoundError(JinjaTemplateError):
17
+ def __init__(self, message: str | None, filename: str, base_template: str | None = None) -> None:
18
+ self.message = message or "Template Not Found"
19
+ self.filename = filename
20
+ self.base_template = base_template
21
+
22
+
23
+ class JinjaTemplateSyntaxError(JinjaTemplateError):
24
+ def __init__(self, message: str | None, lineno: int, filename: str | None = None) -> None:
25
+ self.message = message or "Syntax Error"
26
+ self.filename = filename
27
+ self.lineno = lineno
28
+
29
+
30
+ class JinjaTemplateUndefinedError(JinjaTemplateError):
31
+ def __init__(self, message: str | None, errors: list[UndefinedJinja2Error]) -> None:
32
+ self.message = message or "Undefined Error"
33
+ self.errors = errors
34
+
35
+
36
+ class JinjaTemplateOperationViolationError(JinjaTemplateError):
37
+ def __init__(self, message: str | None = None) -> None:
38
+ self.message = message or "Forbidden code found in the template"
@@ -0,0 +1,151 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class FilterDefinition:
6
+ name: str
7
+ trusted: bool
8
+ source: str
9
+
10
+
11
+ BUILTIN_FILTERS = [
12
+ FilterDefinition(name="abs", trusted=True, source="jinja2"),
13
+ FilterDefinition(name="attr", trusted=False, source="jinja2"),
14
+ FilterDefinition(name="batch", trusted=False, source="jinja2"),
15
+ FilterDefinition(name="capitalize", trusted=True, source="jinja2"),
16
+ FilterDefinition(name="center", trusted=True, source="jinja2"),
17
+ FilterDefinition(name="count", trusted=True, source="jinja2"),
18
+ FilterDefinition(name="d", trusted=True, source="jinja2"),
19
+ FilterDefinition(name="default", trusted=True, source="jinja2"),
20
+ FilterDefinition(name="dictsort", trusted=False, source="jinja2"),
21
+ FilterDefinition(name="e", trusted=True, source="jinja2"),
22
+ FilterDefinition(name="escape", trusted=True, source="jinja2"),
23
+ FilterDefinition(name="filesizeformat", trusted=True, source="jinja2"),
24
+ FilterDefinition(name="first", trusted=True, source="jinja2"),
25
+ FilterDefinition(name="float", trusted=True, source="jinja2"),
26
+ FilterDefinition(name="forceescape", trusted=True, source="jinja2"),
27
+ FilterDefinition(name="format", trusted=True, source="jinja2"),
28
+ FilterDefinition(name="groupby", trusted=False, source="jinja2"),
29
+ FilterDefinition(name="indent", trusted=True, source="jinja2"),
30
+ FilterDefinition(name="int", trusted=True, source="jinja2"),
31
+ FilterDefinition(name="items", trusted=False, source="jinja2"),
32
+ FilterDefinition(name="join", trusted=True, source="jinja2"),
33
+ FilterDefinition(name="last", trusted=True, source="jinja2"),
34
+ FilterDefinition(name="length", trusted=True, source="jinja2"),
35
+ FilterDefinition(name="list", trusted=True, source="jinja2"),
36
+ FilterDefinition(name="lower", trusted=True, source="jinja2"),
37
+ FilterDefinition(name="map", trusted=False, source="jinja2"),
38
+ FilterDefinition(name="max", trusted=True, source="jinja2"),
39
+ FilterDefinition(name="min", trusted=True, source="jinja2"),
40
+ FilterDefinition(name="pprint", trusted=False, source="jinja2"),
41
+ FilterDefinition(name="random", trusted=False, source="jinja2"),
42
+ FilterDefinition(name="reject", trusted=False, source="jinja2"),
43
+ FilterDefinition(name="rejectattr", trusted=False, source="jinja2"),
44
+ FilterDefinition(name="replace", trusted=True, source="jinja2"),
45
+ FilterDefinition(name="reverse", trusted=True, source="jinja2"),
46
+ FilterDefinition(name="round", trusted=True, source="jinja2"),
47
+ FilterDefinition(name="safe", trusted=False, source="jinja2"),
48
+ FilterDefinition(name="select", trusted=False, source="jinja2"),
49
+ FilterDefinition(name="selectattr", trusted=False, source="jinja2"),
50
+ FilterDefinition(name="slice", trusted=True, source="jinja2"),
51
+ FilterDefinition(name="sort", trusted=False, source="jinja2"),
52
+ FilterDefinition(name="string", trusted=True, source="jinja2"),
53
+ FilterDefinition(name="striptags", trusted=True, source="jinja2"),
54
+ FilterDefinition(name="sum", trusted=True, source="jinja2"),
55
+ FilterDefinition(name="title", trusted=True, source="jinja2"),
56
+ FilterDefinition(name="tojson", trusted=False, source="jinja2"),
57
+ FilterDefinition(name="trim", trusted=True, source="jinja2"),
58
+ FilterDefinition(name="truncate", trusted=True, source="jinja2"),
59
+ FilterDefinition(name="unique", trusted=False, source="jinja2"),
60
+ FilterDefinition(name="upper", trusted=True, source="jinja2"),
61
+ FilterDefinition(name="urlencode", trusted=True, source="jinja2"),
62
+ FilterDefinition(name="urlize", trusted=False, source="jinja2"),
63
+ FilterDefinition(name="wordcount", trusted=True, source="jinja2"),
64
+ FilterDefinition(name="wordwrap", trusted=True, source="jinja2"),
65
+ FilterDefinition(name="xmlattr", trusted=False, source="jinja2"),
66
+ ]
67
+
68
+
69
+ NETUTILS_FILTERS = [
70
+ FilterDefinition(name="abbreviated_interface_name", trusted=True, source="netutils"),
71
+ FilterDefinition(name="abbreviated_interface_name_list", trusted=True, source="netutils"),
72
+ FilterDefinition(name="asn_to_int", trusted=True, source="netutils"),
73
+ FilterDefinition(name="bits_to_name", trusted=True, source="netutils"),
74
+ FilterDefinition(name="bytes_to_name", trusted=True, source="netutils"),
75
+ FilterDefinition(name="canonical_interface_name", trusted=True, source="netutils"),
76
+ FilterDefinition(name="canonical_interface_name_list", trusted=True, source="netutils"),
77
+ FilterDefinition(name="cidr_to_netmask", trusted=True, source="netutils"),
78
+ FilterDefinition(name="cidr_to_netmaskv6", trusted=True, source="netutils"),
79
+ FilterDefinition(name="clean_config", trusted=True, source="netutils"),
80
+ FilterDefinition(name="compare_version_loose", trusted=True, source="netutils"),
81
+ FilterDefinition(name="compare_version_strict", trusted=True, source="netutils"),
82
+ FilterDefinition(name="config_compliance", trusted=True, source="netutils"),
83
+ FilterDefinition(name="config_section_not_parsed", trusted=True, source="netutils"),
84
+ FilterDefinition(name="delimiter_change", trusted=True, source="netutils"),
85
+ FilterDefinition(name="diff_network_config", trusted=True, source="netutils"),
86
+ FilterDefinition(name="feature_compliance", trusted=True, source="netutils"),
87
+ FilterDefinition(name="find_unordered_cfg_lines", trusted=True, source="netutils"),
88
+ FilterDefinition(name="fqdn_to_ip", trusted=False, source="netutils"),
89
+ FilterDefinition(name="get_all_host", trusted=False, source="netutils"),
90
+ FilterDefinition(name="get_broadcast_address", trusted=True, source="netutils"),
91
+ FilterDefinition(name="get_first_usable", trusted=True, source="netutils"),
92
+ FilterDefinition(name="get_ips_sorted", trusted=True, source="netutils"),
93
+ FilterDefinition(name="get_nist_urls", trusted=True, source="netutils"),
94
+ FilterDefinition(name="get_nist_vendor_platform_urls", trusted=True, source="netutils"),
95
+ FilterDefinition(name="get_oui", trusted=True, source="netutils"),
96
+ FilterDefinition(name="get_peer_ip", trusted=True, source="netutils"),
97
+ FilterDefinition(name="get_range_ips", trusted=True, source="netutils"),
98
+ FilterDefinition(name="get_upgrade_path", trusted=True, source="netutils"),
99
+ FilterDefinition(name="get_usable_range", trusted=True, source="netutils"),
100
+ FilterDefinition(name="hash_data", trusted=True, source="netutils"),
101
+ FilterDefinition(name="int_to_asdot", trusted=True, source="netutils"),
102
+ FilterDefinition(name="interface_range_compress", trusted=True, source="netutils"),
103
+ FilterDefinition(name="interface_range_expansion", trusted=True, source="netutils"),
104
+ FilterDefinition(name="ip_addition", trusted=True, source="netutils"),
105
+ FilterDefinition(name="ip_subtract", trusted=True, source="netutils"),
106
+ FilterDefinition(name="ip_to_bin", trusted=True, source="netutils"),
107
+ FilterDefinition(name="ip_to_hex", trusted=True, source="netutils"),
108
+ FilterDefinition(name="ipaddress_address", trusted=True, source="netutils"),
109
+ FilterDefinition(name="ipaddress_interface", trusted=True, source="netutils"),
110
+ FilterDefinition(name="ipaddress_network", trusted=True, source="netutils"),
111
+ FilterDefinition(name="is_classful", trusted=True, source="netutils"),
112
+ FilterDefinition(name="is_fqdn_resolvable", trusted=False, source="netutils"),
113
+ FilterDefinition(name="is_ip", trusted=True, source="netutils"),
114
+ FilterDefinition(name="is_ip_range", trusted=True, source="netutils"),
115
+ FilterDefinition(name="is_ip_within", trusted=True, source="netutils"),
116
+ FilterDefinition(name="is_netmask", trusted=True, source="netutils"),
117
+ FilterDefinition(name="is_network", trusted=True, source="netutils"),
118
+ FilterDefinition(name="is_reversible_wildcardmask", trusted=True, source="netutils"),
119
+ FilterDefinition(name="is_valid_mac", trusted=True, source="netutils"),
120
+ FilterDefinition(name="longest_prefix_match", trusted=True, source="netutils"),
121
+ FilterDefinition(name="mac_normalize", trusted=True, source="netutils"),
122
+ FilterDefinition(name="mac_to_format", trusted=True, source="netutils"),
123
+ FilterDefinition(name="mac_to_int", trusted=True, source="netutils"),
124
+ FilterDefinition(name="mac_type", trusted=True, source="netutils"),
125
+ FilterDefinition(name="name_to_bits", trusted=True, source="netutils"),
126
+ FilterDefinition(name="name_to_bytes", trusted=True, source="netutils"),
127
+ FilterDefinition(name="name_to_name", trusted=True, source="netutils"),
128
+ FilterDefinition(name="netmask_to_cidr", trusted=True, source="netutils"),
129
+ FilterDefinition(name="netmask_to_wildcardmask", trusted=True, source="netutils"),
130
+ FilterDefinition(name="normalise_delimiter_caret_c", trusted=True, source="netutils"),
131
+ FilterDefinition(name="paloalto_panos_brace_to_set", trusted=True, source="netutils"),
132
+ FilterDefinition(name="paloalto_panos_clean_newlines", trusted=True, source="netutils"),
133
+ FilterDefinition(name="regex_findall", trusted=False, source="netutils"),
134
+ FilterDefinition(name="regex_match", trusted=False, source="netutils"),
135
+ FilterDefinition(name="regex_search", trusted=False, source="netutils"),
136
+ FilterDefinition(name="regex_split", trusted=False, source="netutils"),
137
+ FilterDefinition(name="regex_sub", trusted=False, source="netutils"),
138
+ FilterDefinition(name="sanitize_config", trusted=True, source="netutils"),
139
+ FilterDefinition(name="section_config", trusted=True, source="netutils"),
140
+ FilterDefinition(name="sort_interface_list", trusted=True, source="netutils"),
141
+ FilterDefinition(name="split_interface", trusted=True, source="netutils"),
142
+ FilterDefinition(name="uptime_seconds_to_string", trusted=True, source="netutils"),
143
+ FilterDefinition(name="uptime_string_to_seconds", trusted=True, source="netutils"),
144
+ FilterDefinition(name="version_metadata", trusted=True, source="netutils"),
145
+ FilterDefinition(name="vlanconfig_to_list", trusted=True, source="netutils"),
146
+ FilterDefinition(name="vlanlist_to_config", trusted=True, source="netutils"),
147
+ FilterDefinition(name="wildcardmask_to_netmask", trusted=True, source="netutils"),
148
+ ]
149
+
150
+
151
+ AVAILABLE_FILTERS = BUILTIN_FILTERS + NETUTILS_FILTERS
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass
2
+
3
+ from rich.syntax import Syntax
4
+ from rich.traceback import Frame
5
+
6
+
7
+ @dataclass
8
+ class UndefinedJinja2Error:
9
+ frame: Frame
10
+ syntax: Syntax
infrahub_sdk/utils.py CHANGED
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import base64
3
4
  import hashlib
4
5
  import json
6
+ import uuid
5
7
  from itertools import groupby
6
8
  from pathlib import Path
7
9
  from typing import TYPE_CHECKING, Any
@@ -25,6 +27,11 @@ if TYPE_CHECKING:
25
27
  from whenever import TimeDelta
26
28
 
27
29
 
30
+ def generate_short_id() -> str:
31
+ """Generate a short unique ID"""
32
+ return base64.urlsafe_b64encode(uuid.uuid4().bytes).rstrip(b"=").decode("ascii").lower()
33
+
34
+
28
35
  def base36encode(number: int) -> str:
29
36
  if not isinstance(number, (int)):
30
37
  raise TypeError("number must be an integer")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: infrahub-server
3
- Version: 1.2.1
3
+ Version: 1.2.3
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
@@ -32,6 +32,7 @@ Requires-Dist: nats-py (>=2.7.2,<3.0.0)
32
32
  Requires-Dist: neo4j (>=5.28,<5.29)
33
33
  Requires-Dist: neo4j-rust-ext (>=5.28,<5.29)
34
34
  Requires-Dist: netaddr (==1.3.0)
35
+ Requires-Dist: netutils (==1.12.0)
35
36
  Requires-Dist: numpy (>=1.24.2,<2.0.0) ; python_version >= "3.9" and python_version < "3.12"
36
37
  Requires-Dist: numpy (>=1.26.2,<2.0.0) ; python_version >= "3.12"
37
38
  Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (==1.28.1)