playground-ls-cli 4.14.1.dev8__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 (112) hide show
  1. localstack_cli/__init__.py +0 -0
  2. localstack_cli/cli/__init__.py +10 -0
  3. localstack_cli/cli/console.py +11 -0
  4. localstack_cli/cli/core_plugin.py +12 -0
  5. localstack_cli/cli/exceptions.py +19 -0
  6. localstack_cli/cli/localstack.py +951 -0
  7. localstack_cli/cli/lpm.py +138 -0
  8. localstack_cli/cli/main.py +22 -0
  9. localstack_cli/cli/plugin.py +39 -0
  10. localstack_cli/cli/plugins.py +134 -0
  11. localstack_cli/cli/profiles.py +65 -0
  12. localstack_cli/config.py +1689 -0
  13. localstack_cli/constants.py +165 -0
  14. localstack_cli/logging/__init__.py +0 -0
  15. localstack_cli/logging/format.py +194 -0
  16. localstack_cli/logging/setup.py +142 -0
  17. localstack_cli/packages/__init__.py +25 -0
  18. localstack_cli/packages/api.py +418 -0
  19. localstack_cli/packages/core.py +416 -0
  20. localstack_cli/pro/__init__.py +0 -0
  21. localstack_cli/pro/core/__init__.py +0 -0
  22. localstack_cli/pro/core/bootstrap/__init__.py +1 -0
  23. localstack_cli/pro/core/bootstrap/auth.py +213 -0
  24. localstack_cli/pro/core/bootstrap/dns_utils.py +55 -0
  25. localstack_cli/pro/core/bootstrap/entitlements.py +117 -0
  26. localstack_cli/pro/core/bootstrap/extensions/__init__.py +3 -0
  27. localstack_cli/pro/core/bootstrap/extensions/__main__.py +106 -0
  28. localstack_cli/pro/core/bootstrap/extensions/autoinstall.py +63 -0
  29. localstack_cli/pro/core/bootstrap/extensions/bootstrap.py +97 -0
  30. localstack_cli/pro/core/bootstrap/extensions/repository.py +374 -0
  31. localstack_cli/pro/core/bootstrap/licensingv2.py +1259 -0
  32. localstack_cli/pro/core/bootstrap/pods/__init__.py +0 -0
  33. localstack_cli/pro/core/bootstrap/pods/api_types.py +17 -0
  34. localstack_cli/pro/core/bootstrap/pods/constants.py +26 -0
  35. localstack_cli/pro/core/bootstrap/pods/remotes/__init__.py +0 -0
  36. localstack_cli/pro/core/bootstrap/pods/remotes/api.py +75 -0
  37. localstack_cli/pro/core/bootstrap/pods/remotes/configs.py +69 -0
  38. localstack_cli/pro/core/bootstrap/pods/remotes/params.py +86 -0
  39. localstack_cli/pro/core/bootstrap/pods_client.py +834 -0
  40. localstack_cli/pro/core/cli/__init__.py +0 -0
  41. localstack_cli/pro/core/cli/auth.py +226 -0
  42. localstack_cli/pro/core/cli/aws.py +16 -0
  43. localstack_cli/pro/core/cli/cli.py +99 -0
  44. localstack_cli/pro/core/cli/click_utils.py +21 -0
  45. localstack_cli/pro/core/cli/cloud_pods.py +465 -0
  46. localstack_cli/pro/core/cli/diff_view.py +41 -0
  47. localstack_cli/pro/core/cli/ephemeral.py +199 -0
  48. localstack_cli/pro/core/cli/extensions.py +492 -0
  49. localstack_cli/pro/core/cli/iam.py +180 -0
  50. localstack_cli/pro/core/cli/license.py +90 -0
  51. localstack_cli/pro/core/cli/localstack.py +118 -0
  52. localstack_cli/pro/core/cli/replicator.py +378 -0
  53. localstack_cli/pro/core/cli/state.py +183 -0
  54. localstack_cli/pro/core/cli/tree_view.py +235 -0
  55. localstack_cli/pro/core/config.py +556 -0
  56. localstack_cli/pro/core/constants.py +54 -0
  57. localstack_cli/pro/core/plugins.py +169 -0
  58. localstack_cli/runtime/__init__.py +6 -0
  59. localstack_cli/runtime/exceptions.py +7 -0
  60. localstack_cli/runtime/hooks.py +73 -0
  61. localstack_cli/testing/__init__.py +1 -0
  62. localstack_cli/testing/config.py +4 -0
  63. localstack_cli/utils/__init__.py +0 -0
  64. localstack_cli/utils/analytics/__init__.py +12 -0
  65. localstack_cli/utils/analytics/cli.py +67 -0
  66. localstack_cli/utils/analytics/client.py +111 -0
  67. localstack_cli/utils/analytics/events.py +30 -0
  68. localstack_cli/utils/analytics/logger.py +48 -0
  69. localstack_cli/utils/analytics/metadata.py +250 -0
  70. localstack_cli/utils/analytics/publisher.py +160 -0
  71. localstack_cli/utils/analytics/service_request_aggregator.py +133 -0
  72. localstack_cli/utils/archives.py +271 -0
  73. localstack_cli/utils/batching.py +258 -0
  74. localstack_cli/utils/bootstrap.py +1418 -0
  75. localstack_cli/utils/checksum.py +313 -0
  76. localstack_cli/utils/collections.py +554 -0
  77. localstack_cli/utils/common.py +229 -0
  78. localstack_cli/utils/container_networking.py +142 -0
  79. localstack_cli/utils/container_utils/__init__.py +0 -0
  80. localstack_cli/utils/container_utils/container_client.py +1585 -0
  81. localstack_cli/utils/container_utils/docker_cmd_client.py +987 -0
  82. localstack_cli/utils/container_utils/docker_sdk_client.py +1018 -0
  83. localstack_cli/utils/crypto.py +294 -0
  84. localstack_cli/utils/docker_utils.py +272 -0
  85. localstack_cli/utils/files.py +327 -0
  86. localstack_cli/utils/functions.py +92 -0
  87. localstack_cli/utils/http.py +326 -0
  88. localstack_cli/utils/json.py +219 -0
  89. localstack_cli/utils/net.py +516 -0
  90. localstack_cli/utils/no_exit_argument_parser.py +19 -0
  91. localstack_cli/utils/numbers.py +49 -0
  92. localstack_cli/utils/objects.py +235 -0
  93. localstack_cli/utils/patch.py +260 -0
  94. localstack_cli/utils/platform.py +77 -0
  95. localstack_cli/utils/run.py +514 -0
  96. localstack_cli/utils/server/__init__.py +0 -0
  97. localstack_cli/utils/server/tcp_proxy.py +108 -0
  98. localstack_cli/utils/serving.py +187 -0
  99. localstack_cli/utils/ssl.py +71 -0
  100. localstack_cli/utils/strings.py +245 -0
  101. localstack_cli/utils/sync.py +267 -0
  102. localstack_cli/utils/threads.py +163 -0
  103. localstack_cli/utils/time.py +81 -0
  104. localstack_cli/utils/urls.py +21 -0
  105. localstack_cli/utils/venv.py +100 -0
  106. localstack_cli/utils/xml.py +41 -0
  107. localstack_cli/version.py +34 -0
  108. playground_ls_cli-4.14.1.dev8.dist-info/METADATA +95 -0
  109. playground_ls_cli-4.14.1.dev8.dist-info/RECORD +112 -0
  110. playground_ls_cli-4.14.1.dev8.dist-info/WHEEL +5 -0
  111. playground_ls_cli-4.14.1.dev8.dist-info/entry_points.txt +17 -0
  112. playground_ls_cli-4.14.1.dev8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,267 @@
1
+ """Concurrency synchronization utilities"""
2
+
3
+ import functools
4
+ import threading
5
+ import time
6
+ from collections import defaultdict
7
+ from collections.abc import Callable
8
+ from typing import Literal, TypeVar
9
+
10
+
11
+ class ShortCircuitWaitException(Exception):
12
+ """raise to immediately stop waiting, e.g. when an operation permanently failed"""
13
+
14
+ pass
15
+
16
+
17
+ def wait_until(
18
+ fn: Callable[[], bool],
19
+ wait: float = 1.0,
20
+ max_retries: int = 10,
21
+ strategy: Literal["exponential", "static", "linear"] = "exponential",
22
+ _retries: int = 1,
23
+ _max_wait: float = 240,
24
+ ) -> bool:
25
+ """waits until a given condition is true, rechecking it periodically"""
26
+ assert _retries > 0
27
+ if max_retries < _retries:
28
+ return False
29
+ try:
30
+ completed = fn()
31
+ except ShortCircuitWaitException:
32
+ return False
33
+ except Exception:
34
+ completed = False
35
+
36
+ if completed:
37
+ return True
38
+ else:
39
+ if wait > _max_wait:
40
+ return False
41
+ time.sleep(wait)
42
+ next_wait = wait # default: static
43
+ if strategy == "linear":
44
+ next_wait = (wait / _retries) * (_retries + 1)
45
+ elif strategy == "exponential":
46
+ next_wait = wait * 2
47
+ return wait_until(fn, next_wait, max_retries, strategy, _retries + 1, _max_wait)
48
+
49
+
50
+ T = TypeVar("T")
51
+
52
+
53
+ def retry(function: Callable[..., T], retries=3, sleep=1.0, sleep_before=0, **kwargs) -> T:
54
+ raise_error = None
55
+ if sleep_before > 0:
56
+ time.sleep(sleep_before)
57
+ retries = int(retries)
58
+ for i in range(0, retries + 1):
59
+ try:
60
+ return function(**kwargs)
61
+ except Exception as error:
62
+ raise_error = error
63
+ time.sleep(sleep)
64
+ raise raise_error
65
+
66
+
67
+ def poll_condition(condition, timeout: float = None, interval: float = 0.5) -> bool:
68
+ """
69
+ Poll evaluates the given condition until a truthy value is returned. It does this every `interval` seconds
70
+ (0.5 by default), until the timeout (in seconds, if any) is reached.
71
+
72
+ Poll returns True once `condition()` returns a truthy value, or False if the timeout is reached.
73
+ """
74
+ remaining = 0
75
+ if timeout is not None:
76
+ remaining = timeout
77
+
78
+ while not condition():
79
+ if timeout is not None:
80
+ remaining -= interval
81
+
82
+ if remaining <= 0:
83
+ return False
84
+
85
+ time.sleep(interval)
86
+
87
+ return True
88
+
89
+
90
+ def synchronized(lock=None):
91
+ """
92
+ Synchronization decorator as described in
93
+ http://blog.dscpl.com.au/2014/01/the-missing-synchronized-decorator.html.
94
+ """
95
+
96
+ def _decorator(wrapped):
97
+ @functools.wraps(wrapped)
98
+ def _wrapper(*args, **kwargs):
99
+ with lock:
100
+ return wrapped(*args, **kwargs)
101
+
102
+ return _wrapper
103
+
104
+ return _decorator
105
+
106
+
107
+ def sleep_forever():
108
+ while True:
109
+ time.sleep(1)
110
+
111
+
112
+ class SynchronizedDefaultDict(defaultdict):
113
+ def __init__(self, *args, **kwargs):
114
+ super().__init__(*args, **kwargs)
115
+ self._lock = threading.RLock()
116
+
117
+ def fromkeys(self, keys, value=None):
118
+ with self._lock:
119
+ return super().fromkeys(keys, value)
120
+
121
+ def __getitem__(self, key):
122
+ with self._lock:
123
+ return super().__getitem__(key)
124
+
125
+ def __setitem__(self, key, value):
126
+ with self._lock:
127
+ super().__setitem__(key, value)
128
+
129
+ def __delitem__(self, key):
130
+ with self._lock:
131
+ super().__delitem__(key)
132
+
133
+ def __iter__(self):
134
+ with self._lock:
135
+ return super().__iter__()
136
+
137
+ def __len__(self):
138
+ with self._lock:
139
+ return super().__len__()
140
+
141
+ def __str__(self):
142
+ with self._lock:
143
+ return super().__str__()
144
+
145
+
146
+ class Once:
147
+ """
148
+ An object that will perform an action exactly once.
149
+ Inspired by Golang's [sync.Once](https://pkg.go.dev/sync#Once) operation.
150
+
151
+
152
+ ### Example 1
153
+
154
+ Multiple threads using `Once::do` to ensure only 1 line is printed.
155
+
156
+ ```python
157
+ import threading
158
+ import time
159
+ import random
160
+
161
+ greet_once = Once()
162
+ def greet():
163
+ print("This should happen only once.")
164
+
165
+ greet_threads = []
166
+ for _ in range(10):
167
+ t = threading.Thread(target=lambda: greet_once.do(greet))
168
+ greet_threads.append(t)
169
+ t.start()
170
+
171
+ for t in greet_threads:
172
+ t.join()
173
+ ```
174
+
175
+
176
+ ### Example 2
177
+
178
+ Ensuring idemponent calling to prevent exceptions on multiple calls.
179
+
180
+ ```python
181
+ import os
182
+
183
+ class Service:
184
+ close_once: sync.Once
185
+
186
+ def start(self):
187
+ with open("my-service.txt) as f:
188
+ myfile.write("Started service")
189
+
190
+ def close(self):
191
+ # Ensure we only ever delete the file once on close
192
+ self.close_once.do(lambda: os.remove("my-service.txt"))
193
+
194
+ ```
195
+
196
+
197
+ """
198
+
199
+ _is_done: bool = False
200
+ _mu: threading.Lock = threading.Lock()
201
+
202
+ def do(self, fn: Callable[[], None]):
203
+ """
204
+ `do` calls the function `fn()` if-and-only-if `do` has never been called before.
205
+
206
+ This ensures idempotent and thread-safe execution.
207
+
208
+ If the function raises an exception, `do` considers `fn` as done, where subsequent calls are still no-ops.
209
+ """
210
+ if self._is_done:
211
+ return
212
+
213
+ with self._mu:
214
+ if not self._is_done:
215
+ try:
216
+ fn()
217
+ finally:
218
+ self._is_done = True
219
+
220
+
221
+ def once_func(fn: Callable[..., T]) -> Callable[..., T | None]:
222
+ """
223
+ Wraps and returns a function that can only ever execute once.
224
+
225
+ The first call to the returned function will permanently set the result.
226
+ If the wrapped function raises an exception, this will be re-raised on each subsequent call.
227
+
228
+ This function can be used either as a decorator or called directly.
229
+
230
+ Direct usage:
231
+ ```python
232
+ delete_file = once_func(os.remove)
233
+
234
+ delete_file("myfile.txt") # deletes the file
235
+ delete_file("myfile.txt") # does nothing
236
+ ```
237
+
238
+ As a decorator:
239
+ ```python
240
+ @once_func
241
+ def delete_file():
242
+ os.remove("myfile.txt")
243
+
244
+ delete_file() # deletes the file
245
+ delete_file() # does nothing
246
+ ```
247
+ """
248
+ once = Once()
249
+
250
+ result, exception = None, None
251
+
252
+ def _do(*args, **kwargs):
253
+ nonlocal result, exception
254
+ try:
255
+ result = fn(*args, **kwargs)
256
+ except Exception as e:
257
+ exception = e
258
+ raise
259
+
260
+ @functools.wraps(fn)
261
+ def wrapper(*args, **kwargs):
262
+ once.do(lambda: _do(*args, **kwargs))
263
+ if exception is not None:
264
+ raise exception
265
+ return result
266
+
267
+ return wrapper
@@ -0,0 +1,163 @@
1
+ import concurrent.futures
2
+ import inspect
3
+ import logging
4
+ import threading
5
+ import traceback
6
+ from collections.abc import Callable
7
+ from concurrent.futures import Future
8
+ from multiprocessing.dummy import Pool
9
+
10
+ LOG = logging.getLogger(__name__)
11
+
12
+ # arrays for temporary threads and resources
13
+ TMP_THREADS = []
14
+ TMP_PROCESSES = []
15
+
16
+ counter_lock = threading.Lock()
17
+ counter = 0
18
+
19
+
20
+ class FuncThread(threading.Thread):
21
+ """Helper class to run a Python function in a background thread."""
22
+
23
+ def __init__(
24
+ self,
25
+ func,
26
+ params=None,
27
+ quiet=False,
28
+ on_stop: Callable[["FuncThread"], None] = None,
29
+ name: str | None = None,
30
+ daemon=True,
31
+ ):
32
+ global counter
33
+ global counter_lock
34
+
35
+ if name:
36
+ with counter_lock:
37
+ counter += 1
38
+ thread_counter_current = counter
39
+
40
+ threading.Thread.__init__(
41
+ self, name=f"{name}-functhread{thread_counter_current}", daemon=daemon
42
+ )
43
+ else:
44
+ threading.Thread.__init__(self, daemon=daemon)
45
+
46
+ self.params = params
47
+ self.func = func
48
+ self.quiet = quiet
49
+ self.result_future = Future()
50
+ self._stop_event = threading.Event()
51
+ self.on_stop = on_stop
52
+
53
+ def run(self):
54
+ result = None
55
+ try:
56
+ kwargs = {}
57
+ argspec = inspect.getfullargspec(self.func)
58
+ if argspec.varkw or "_thread" in (argspec.args or []) + (argspec.kwonlyargs or []):
59
+ kwargs["_thread"] = self
60
+ result = self.func(self.params, **kwargs)
61
+ except Exception as e:
62
+ self.result_future.set_exception(e)
63
+ result = e
64
+ if not self.quiet:
65
+ LOG.info(
66
+ "Thread run method %s(%s) failed: %s %s",
67
+ self.func,
68
+ self.params,
69
+ e,
70
+ traceback.format_exc(),
71
+ )
72
+ finally:
73
+ try:
74
+ self.result_future.set_result(result)
75
+ pass
76
+ except concurrent.futures.InvalidStateError as e:
77
+ # this can happen on shutdown if the task is already canceled
78
+ LOG.debug(e)
79
+
80
+ @property
81
+ def running(self):
82
+ return not self._stop_event.is_set()
83
+
84
+ def stop(self, quiet: bool = False) -> None:
85
+ self._stop_event.set()
86
+
87
+ if self.on_stop:
88
+ try:
89
+ self.on_stop(self)
90
+ except Exception as e:
91
+ LOG.warning("error while calling on_stop callback: %s", e)
92
+
93
+
94
+ def start_thread(method, *args, **kwargs) -> FuncThread: # TODO: find all usages and add names...
95
+ """Start the given method in a background thread, and add the thread to the TMP_THREADS shutdown hook"""
96
+ _shutdown_hook = kwargs.pop("_shutdown_hook", True)
97
+ if not kwargs.get("name"):
98
+ LOG.debug(
99
+ "start_thread called without providing a custom name"
100
+ ) # technically we should add a new level here for *internal* warnings
101
+ kwargs.setdefault("name", method.__name__)
102
+ thread = FuncThread(method, *args, **kwargs)
103
+ thread.start()
104
+ if _shutdown_hook:
105
+ TMP_THREADS.append(thread)
106
+ return thread
107
+
108
+
109
+ def start_worker_thread(method, *args, **kwargs):
110
+ kwargs.setdefault("name", "start_worker_thread")
111
+ return start_thread(method, *args, _shutdown_hook=False, **kwargs)
112
+
113
+
114
+ def cleanup_threads_and_processes(quiet=True):
115
+ from localstack_cli.utils.run import kill_process_tree
116
+
117
+ for thread in TMP_THREADS:
118
+ if thread:
119
+ try:
120
+ if hasattr(thread, "shutdown"):
121
+ thread.shutdown()
122
+ continue
123
+ if hasattr(thread, "kill"):
124
+ thread.kill()
125
+ continue
126
+ thread.stop(quiet=quiet)
127
+ except Exception as e:
128
+ LOG.debug("[shutdown] Error stopping thread %s: %s", thread, e)
129
+ if not thread.daemon:
130
+ LOG.warning(
131
+ "[shutdown] Non-daemon thread %s may block localstack shutdown", thread
132
+ )
133
+ for proc in TMP_PROCESSES:
134
+ try:
135
+ kill_process_tree(proc.pid)
136
+ # proc.terminate()
137
+ except Exception as e:
138
+ LOG.debug("[shutdown] Error cleaning up process tree %s: %s", proc, e)
139
+ # clean up async tasks
140
+ try:
141
+ import asyncio
142
+
143
+ for task in asyncio.all_tasks():
144
+ try:
145
+ task.cancel()
146
+ except Exception as e:
147
+ LOG.debug("[shutdown] Error cancelling asyncio task %s: %s", task, e)
148
+ except Exception:
149
+ pass
150
+ LOG.debug("[shutdown] Done cleaning up threads / processes / tasks")
151
+ # clear lists
152
+ TMP_THREADS.clear()
153
+ TMP_PROCESSES.clear()
154
+
155
+
156
+ def parallelize(func: Callable, arr: list, size: int = None):
157
+ if not size:
158
+ size = len(arr)
159
+ if size <= 0:
160
+ return None
161
+
162
+ with Pool(size) as pool:
163
+ return pool.map(func, arr)
@@ -0,0 +1,81 @@
1
+ import time
2
+ from datetime import date, datetime, timezone, tzinfo
3
+ from zoneinfo import ZoneInfo
4
+
5
+ TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S"
6
+ TIMESTAMP_FORMAT_TZ = "%Y-%m-%dT%H:%M:%SZ"
7
+ TIMESTAMP_FORMAT_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ"
8
+ TIMESTAMP_READABLE_FORMAT = "%d/%b/%Y:%H:%M:%S %z"
9
+
10
+
11
+ def isoformat_milliseconds(t) -> str:
12
+ try:
13
+ return t.isoformat(timespec="milliseconds")
14
+ except TypeError:
15
+ return t.isoformat()[:-3]
16
+
17
+
18
+ def timestamp(time=None, format: str = TIMESTAMP_FORMAT) -> str:
19
+ if not time:
20
+ time = datetime.utcnow()
21
+ if isinstance(time, (int, float)):
22
+ time = datetime.fromtimestamp(time)
23
+ return time.strftime(format)
24
+
25
+
26
+ def timestamp_millis(time=None) -> str:
27
+ microsecond_time = timestamp(time=time, format=TIMESTAMP_FORMAT_MICROS)
28
+ # truncating microseconds to milliseconds, while leaving the "Z" indicator
29
+ return microsecond_time[:-4] + microsecond_time[-1]
30
+
31
+
32
+ def iso1806_to_epoch(t: str) -> float:
33
+ return datetime.fromisoformat(t).timestamp()
34
+
35
+
36
+ def epoch_to_iso1806(ts: int) -> str:
37
+ return datetime.utcfromtimestamp(ts).isoformat()
38
+
39
+
40
+ def epoch_timestamp() -> float:
41
+ return time.time()
42
+
43
+
44
+ def parse_timestamp(ts_str: str) -> datetime:
45
+ """
46
+ Parse the incoming date string into a timezone aware datetime object
47
+ :param ts_str:
48
+ :return:
49
+ """
50
+ for ts_format in [
51
+ TIMESTAMP_FORMAT,
52
+ TIMESTAMP_FORMAT_TZ,
53
+ TIMESTAMP_FORMAT_MICROS,
54
+ TIMESTAMP_READABLE_FORMAT,
55
+ ]:
56
+ try:
57
+ value = datetime.strptime(ts_str, ts_format)
58
+ if value.tzinfo is None:
59
+ value = value.replace(tzinfo=ZoneInfo("UTC"))
60
+ return value
61
+ except ValueError:
62
+ pass
63
+ raise Exception(f"Unable to parse timestamp string with any known formats: {ts_str}")
64
+
65
+
66
+ def now(millis: bool = False, tz: tzinfo | None = None) -> int:
67
+ return mktime(datetime.now(tz=tz), millis=millis)
68
+
69
+
70
+ def now_utc(millis: bool = False) -> int:
71
+ return now(millis, timezone.utc)
72
+
73
+
74
+ def today_no_time() -> int:
75
+ return mktime(datetime.combine(date.today(), datetime.min.time()))
76
+
77
+
78
+ def mktime(ts: datetime, millis: bool = False) -> int:
79
+ if millis:
80
+ return int(ts.timestamp() * 1000)
81
+ return int(ts.timestamp())
@@ -0,0 +1,21 @@
1
+ from localstack_cli import config
2
+ from localstack_cli.config import HostAndPort
3
+
4
+
5
+ def path_from_url(url: str) -> str:
6
+ return f"/{url.partition('://')[2].partition('/')[2]}" if "://" in url else url
7
+
8
+
9
+ def hostname_from_url(url: str) -> str:
10
+ return url.split("://")[-1].split("/")[0].split(":")[0]
11
+
12
+
13
+ def localstack_host(custom_port: int | None = None) -> HostAndPort:
14
+ """
15
+ Determine the host and port to return to the user based on:
16
+ - the user's configuration (e.g environment variable overrides)
17
+ - the defaults of the system
18
+ """
19
+ port = custom_port or config.LOCALSTACK_HOST.port
20
+ host = config.LOCALSTACK_HOST.host
21
+ return HostAndPort(host=host, port=port)
@@ -0,0 +1,100 @@
1
+ import io
2
+ import os
3
+ import sys
4
+ from functools import cached_property
5
+ from pathlib import Path
6
+ from typing import Union
7
+
8
+
9
+ class VirtualEnvironment:
10
+ """
11
+ Encapsulates methods to operate and navigate on a python virtual environment.
12
+ """
13
+
14
+ def __init__(self, venv_dir: Union[str, os.PathLike]):
15
+ self._venv_dir = venv_dir
16
+
17
+ def create(self) -> None:
18
+ """
19
+ Uses the virtualenv cli to create the virtual environment.
20
+ :return:
21
+ """
22
+ self.venv_dir.mkdir(parents=True, exist_ok=True)
23
+ from venv import main
24
+
25
+ main([str(self.venv_dir)])
26
+
27
+ @property
28
+ def exists(self) -> bool:
29
+ """
30
+ Checks whether the virtual environment exists by checking whether the site-package directory of the venv exists.
31
+ :return: the if the venv exists
32
+ :raises NotADirectoryError: if the venv path exists but is not a directory
33
+ """
34
+ try:
35
+ return True if self.site_dir else False
36
+ except FileNotFoundError:
37
+ return False
38
+
39
+ @cached_property
40
+ def venv_dir(self) -> Path:
41
+ """
42
+ Returns the path of the virtual environment directory
43
+ :return: the path to the venv
44
+ """
45
+ return Path(self._venv_dir).absolute()
46
+
47
+ @cached_property
48
+ def site_dir(self) -> Path:
49
+ """
50
+ Resolves and returns the site-packages directory of the virtual environment. Once resolved successfully the
51
+ result is cached.
52
+
53
+ :return: the path to the site-packages dir.
54
+ :raise FileNotFoundError: if the venv does not exist or the site-packages could not be found, or there are
55
+ multiple lib/python* directories.
56
+ :raise NotADirectoryError: if the venv is not a directory
57
+ """
58
+ venv = self.venv_dir
59
+
60
+ if not venv.exists():
61
+ raise FileNotFoundError(f"expected venv directory to exist at {venv}")
62
+
63
+ if not venv.is_dir():
64
+ raise NotADirectoryError(f"expected {venv} to be a directory")
65
+
66
+ matches = list(venv.glob("lib/python*/site-packages"))
67
+
68
+ if not matches:
69
+ raise FileNotFoundError(f"could not find site-packages directory in {venv}")
70
+
71
+ if len(matches) > 1:
72
+ raise FileNotFoundError(f"multiple python versions found in {venv}: {matches}")
73
+
74
+ return matches[0]
75
+
76
+ def inject_to_sys_path(self) -> None:
77
+ path = str(self.site_dir)
78
+ if path and path not in sys.path:
79
+ sys.path.append(path)
80
+
81
+ def add_pth(self, name, path: Union[str, os.PathLike, "VirtualEnvironment"]) -> None:
82
+ """
83
+ Add a <name>.pth file into the virtual environment and append the given path to it. Does nothing if the path
84
+ is already in the file.
85
+
86
+ :param name: the name of the path file (without the .pth extensions)
87
+ :param path: the path to be appended
88
+ """
89
+ pth_file = self.site_dir / f"{name}.pth"
90
+
91
+ if isinstance(path, VirtualEnvironment):
92
+ path = path.site_dir
93
+
94
+ line = io.text_encoding(str(path)) + "\n"
95
+
96
+ if pth_file.exists() and line in pth_file.read_text():
97
+ return
98
+
99
+ with open(pth_file, "a") as fd:
100
+ fd.write(line)
@@ -0,0 +1,41 @@
1
+ import xml.etree.ElementTree as ET
2
+ from typing import Any
3
+
4
+
5
+ def obj_to_xml(obj: Any) -> str:
6
+ """Return an XML representation of the given object (dict, list, or primitive).
7
+ Does NOT add a common root element if the given obj is a list.
8
+ Does NOT work for nested dict structures."""
9
+ if isinstance(obj, list):
10
+ return "".join([obj_to_xml(o) for o in obj])
11
+ if isinstance(obj, dict):
12
+ return "".join([f"<{k}>{obj_to_xml(v)}</{k}>" for (k, v) in obj.items()])
13
+ return str(obj)
14
+
15
+
16
+ def strip_xmlns(obj: Any) -> Any:
17
+ """Strip xmlns attributes from a dict returned by xmltodict.parse."""
18
+ if isinstance(obj, list):
19
+ return [strip_xmlns(item) for item in obj]
20
+ if isinstance(obj, dict):
21
+ # Remove xmlns attribute.
22
+ obj.pop("@xmlns", None)
23
+ if len(obj) == 1 and "#text" in obj:
24
+ # If the only remaining key is the #text key, elide the dict
25
+ # entirely, to match the structure that xmltodict.parse would have
26
+ # returned if the xmlns namespace hadn't been present.
27
+ return obj["#text"]
28
+ return {k: strip_xmlns(v) for k, v in obj.items()}
29
+ return obj
30
+
31
+
32
+ def is_valid_xml(xml_string: str) -> bool:
33
+ """
34
+ Check if the given string is a valid XML document.
35
+ """
36
+ try:
37
+ # Attempt to parse the XML string
38
+ ET.fromstring(xml_string.encode("utf-8"))
39
+ return True
40
+ except ET.ParseError:
41
+ return False
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '4.14.1.dev8'
32
+ __version_tuple__ = version_tuple = (4, 14, 1, 'dev8')
33
+
34
+ __commit_id__ = commit_id = None