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,235 @@
1
+ import functools
2
+ import re
3
+ import threading
4
+ from collections.abc import Callable
5
+ from typing import Any, Generic, TypeVar
6
+
7
+ from .collections import ensure_list
8
+ from .strings import first_char_to_lower, first_char_to_upper
9
+
10
+ ComplexType = list | dict | object
11
+
12
+ _T = TypeVar("_T")
13
+
14
+
15
+ class Value(Generic[_T]):
16
+ """
17
+ Simple value container.
18
+ """
19
+
20
+ value: _T | None
21
+
22
+ def __init__(self, value: _T = None) -> None:
23
+ self.value = value
24
+
25
+ def clear(self):
26
+ self.value = None
27
+
28
+ def set(self, value: _T):
29
+ self.value = value
30
+
31
+ def is_set(self) -> bool:
32
+ return self.value is not None
33
+
34
+ def get(self) -> _T | None:
35
+ return self.value
36
+
37
+ def __bool__(self):
38
+ return True if self.value else False
39
+
40
+
41
+ class ArbitraryAccessObj:
42
+ """Dummy object that can be arbitrarily accessed - any attributes, as a callable, item assignment, ..."""
43
+
44
+ def __init__(self, name=None):
45
+ self.name = name
46
+
47
+ def __getattr__(self, name, *args, **kwargs):
48
+ return ArbitraryAccessObj(name)
49
+
50
+ def __call__(self, *args, **kwargs):
51
+ if self.name in ["items", "keys", "values"] and not args and not kwargs:
52
+ return []
53
+ return ArbitraryAccessObj()
54
+
55
+ def __getitem__(self, *args, **kwargs):
56
+ return ArbitraryAccessObj()
57
+
58
+ def __setitem__(self, *args, **kwargs):
59
+ return ArbitraryAccessObj()
60
+
61
+
62
+ class Mock:
63
+ """Dummy class that can be used for mocking custom attributes."""
64
+
65
+ pass
66
+
67
+
68
+ class ObjectIdHashComparator:
69
+ """Simple wrapper class that allows us to create a hashset using the object id(..) as the entries' hash value"""
70
+
71
+ def __init__(self, obj):
72
+ self.obj = obj
73
+ self._hash = id(obj)
74
+
75
+ def __hash__(self):
76
+ return self._hash
77
+
78
+ def __eq__(self, other):
79
+ # assumption here is that we're comparing only against ObjectIdHash instances!
80
+ return self.obj == other.obj
81
+
82
+
83
+ class SubtypesInstanceManager:
84
+ """Simple instance manager base class that scans the subclasses of a base type for concrete named
85
+ implementations, and lazily creates and returns (singleton) instances on demand."""
86
+
87
+ _instances: dict[str, "SubtypesInstanceManager"]
88
+
89
+ @classmethod
90
+ def get(cls, subtype_name: str, raise_if_missing: bool = True):
91
+ instances = cls.instances()
92
+ base_type = cls.get_base_type()
93
+ instance = instances.get(subtype_name)
94
+ if instance is None:
95
+ # lazily load subtype instance (required if new plugins are dynamically loaded at runtime)
96
+ for clazz in get_all_subclasses(base_type):
97
+ impl_name = clazz.impl_name()
98
+ if impl_name not in instances and subtype_name == impl_name:
99
+ instances[impl_name] = clazz()
100
+ instance = instances.get(subtype_name)
101
+ if not instance and raise_if_missing:
102
+ raise NotImplementedError(
103
+ f"Unable to find implementation named '{subtype_name}' for base type {base_type}"
104
+ )
105
+ return instance
106
+
107
+ @classmethod
108
+ def instances(cls) -> dict[str, "SubtypesInstanceManager"]:
109
+ base_type = cls.get_base_type()
110
+ if not hasattr(base_type, "_instances"):
111
+ base_type._instances = {}
112
+ return base_type._instances
113
+
114
+ @staticmethod
115
+ def impl_name() -> str:
116
+ """Name of this concrete subtype - to be implemented by subclasses."""
117
+ raise NotImplementedError
118
+
119
+ @classmethod
120
+ def get_base_type(cls) -> type:
121
+ """Get the base class for which instances are being managed - can be customized by subtypes."""
122
+ return cls
123
+
124
+
125
+ # this requires that all subclasses have been imported before(!)
126
+ def get_all_subclasses(clazz: type) -> set[type]:
127
+ """Recursively get all subclasses of the given class."""
128
+ result = set()
129
+ subs = clazz.__subclasses__()
130
+ for sub in subs:
131
+ result.add(sub)
132
+ result.update(get_all_subclasses(sub))
133
+ return result
134
+
135
+
136
+ def fully_qualified_class_name(klass: type) -> str:
137
+ return f"{klass.__module__}.{klass.__name__}"
138
+
139
+
140
+ def not_none_or(value: Any | None, alternative: Any) -> Any:
141
+ """Return 'value' if it is not None, or 'alternative' otherwise."""
142
+ return value if value is not None else alternative
143
+
144
+
145
+ def recurse_object(obj: ComplexType, func: Callable, path: str = "") -> ComplexType:
146
+ """Recursively apply `func` to `obj` (might be a list, dict, or other object)."""
147
+ obj = func(obj, path=path)
148
+ if isinstance(obj, list):
149
+ for i in range(len(obj)):
150
+ tmp_path = f"{path or '.'}[{i}]"
151
+ obj[i] = recurse_object(obj[i], func, tmp_path)
152
+ elif isinstance(obj, dict):
153
+ for k, v in obj.items():
154
+ tmp_path = f"{f'{path}.' if path else ''}{k}"
155
+ obj[k] = recurse_object(v, func, tmp_path)
156
+ return obj
157
+
158
+
159
+ def keys_to(
160
+ obj: ComplexType, op: Callable[[str], str], skip_children_of: list[str] = None
161
+ ) -> ComplexType:
162
+ """Recursively changes all dict keys to apply op. Skip children
163
+ of any elements whose names are contained in skip_children_of (e.g., ['Tags'])"""
164
+ skip_children_of = ensure_list(skip_children_of or [])
165
+
166
+ def fix_keys(o, path="", **kwargs):
167
+ if any(re.match(rf"(^|.*\.){k}($|[.\[].*)", path) for k in skip_children_of):
168
+ return o
169
+ if isinstance(o, dict):
170
+ for k, v in dict(o).items():
171
+ o.pop(k)
172
+ o[op(k)] = v
173
+ return o
174
+
175
+ result = recurse_object(obj, fix_keys)
176
+ return result
177
+
178
+
179
+ def keys_to_lower(obj: ComplexType, skip_children_of: list[str] = None) -> ComplexType:
180
+ return keys_to(obj, first_char_to_lower, skip_children_of)
181
+
182
+
183
+ def keys_to_upper(obj: ComplexType, skip_children_of: list[str] = None) -> ComplexType:
184
+ return keys_to(obj, first_char_to_upper, skip_children_of)
185
+
186
+
187
+ def singleton_factory(factory: Callable[[], _T]) -> Callable[[], _T]:
188
+ """
189
+ Decorator for methods that create a particular value once and then return the same value in a thread safe way.
190
+
191
+ :param factory: the method to decorate
192
+ :return: a threadsafe singleton factory
193
+ """
194
+ lock = threading.RLock()
195
+ instance: Value[_T] = Value()
196
+
197
+ @functools.wraps(factory)
198
+ def _singleton_factory() -> _T:
199
+ if instance.is_set():
200
+ return instance.get()
201
+
202
+ with lock:
203
+ if not instance:
204
+ instance.set(factory())
205
+
206
+ return instance.get()
207
+
208
+ _singleton_factory.clear = instance.clear
209
+
210
+ return _singleton_factory
211
+
212
+
213
+ def get_value_from_path(data, path):
214
+ keys = path.split(".")
215
+ try:
216
+ result = functools.reduce(dict.__getitem__, keys, data)
217
+ return result
218
+ except KeyError:
219
+ # Handle the case where the path is not valid for the given dictionary
220
+ return None
221
+
222
+
223
+ def set_value_at_path(data, path, new_value):
224
+ keys = path.split(".")
225
+ last_key = keys[-1]
226
+
227
+ # Traverse the dictionary to the second-to-last level
228
+ nested_dict = functools.reduce(dict.__getitem__, keys[:-1], data)
229
+
230
+ try:
231
+ # Set the new value
232
+ nested_dict[last_key] = new_value
233
+ except KeyError:
234
+ # Handle the case where the path is not valid for the given dictionary
235
+ raise ValueError(f"Invalid path: {path}")
@@ -0,0 +1,260 @@
1
+ import functools
2
+ import inspect
3
+ import types
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+
7
+
8
+ def get_defining_object(method):
9
+ """Returns either the class or the module that defines the given function/method."""
10
+ # adapted from https://stackoverflow.com/a/25959545/804840
11
+ if inspect.ismethod(method):
12
+ return method.__self__
13
+
14
+ if inspect.isfunction(method):
15
+ class_name = method.__qualname__.split(".<locals>", 1)[0].rsplit(".", 1)[0]
16
+ try:
17
+ # method is not bound but referenced by a class, like MyClass.mymethod
18
+ cls = getattr(inspect.getmodule(method), class_name)
19
+ except AttributeError:
20
+ cls = method.__globals__.get(class_name)
21
+
22
+ if isinstance(cls, type):
23
+ return cls
24
+
25
+ # method is a module-level function
26
+ return inspect.getmodule(method)
27
+
28
+
29
+ def to_metadata_string(obj: Any) -> str:
30
+ """
31
+ Creates a string that helps humans understand where the given object comes from. Examples::
32
+
33
+ to_metadata_string(func_thread.run) == "function(localstack.utils.threads:FuncThread.run)"
34
+
35
+ :param obj: a class, module, method, function or object
36
+ :return: a string representing the objects origin
37
+ """
38
+ if inspect.isclass(obj):
39
+ return f"class({obj.__module__}:{obj.__name__})"
40
+ if inspect.ismodule(obj):
41
+ return f"module({obj.__name__})"
42
+ if inspect.ismethod(obj):
43
+ return f"method({obj.__module__}:{obj.__qualname__})"
44
+ if inspect.isfunction(obj):
45
+ # TODO: distinguish bound method
46
+ return f"function({obj.__module__}:{obj.__qualname__})"
47
+ if isinstance(obj, object):
48
+ return f"object({obj.__module__}:{obj.__class__.__name__})"
49
+ return str(obj)
50
+
51
+
52
+ def create_patch_proxy(target: Callable, new: Callable):
53
+ """
54
+ Creates a proxy that calls `new` but passes as first argument the target.
55
+ """
56
+
57
+ @functools.wraps(target)
58
+ def proxy(*args, **kwargs):
59
+ if _is_bound_method:
60
+ # bound object "self" is passed as first argument if this is a bound method
61
+ args = args[1:]
62
+ return new(target, *args, **kwargs)
63
+
64
+ # keep track of the real proxy subject (i.e., the new function that is used as patch)
65
+ proxy.__subject__ = new
66
+
67
+ _is_bound_method = inspect.ismethod(target)
68
+ if _is_bound_method:
69
+ proxy = types.MethodType(proxy, target.__self__)
70
+
71
+ return proxy
72
+
73
+
74
+ class Patch:
75
+ applied_patches: list["Patch"] = []
76
+ """Bookkeeping for patches that are applied. You can use this to debug patches. For instance,
77
+ you could write something like::
78
+
79
+ for patch in Patch.applied_patches:
80
+ print(patch)
81
+
82
+ Which will output in a human readable format information about the currently active patches.
83
+ """
84
+
85
+ obj: Any
86
+ name: str
87
+ new: Any
88
+
89
+ def __init__(self, obj: Any, name: str, new: Any) -> None:
90
+ super().__init__()
91
+ self.obj = obj
92
+ self.name = name
93
+ try:
94
+ self.old = getattr(self.obj, name)
95
+ except AttributeError:
96
+ self.old = None
97
+ self.new = new
98
+ self.is_applied = False
99
+
100
+ def apply(self):
101
+ if self.is_applied:
102
+ return
103
+
104
+ if self.old and self.name == "__getattr__":
105
+ raise Exception("You can't patch class types implementing __getattr__")
106
+ if not self.old and self.name != "__getattr__":
107
+ raise AttributeError(f"`{self.obj.__name__}` object has no attribute `{self.name}`")
108
+ setattr(self.obj, self.name, self.new)
109
+ Patch.applied_patches.append(self)
110
+ self.is_applied = True
111
+
112
+ def undo(self):
113
+ if not self.is_applied:
114
+ return
115
+
116
+ # If we added a method to a class type, we don't have a self.old. We just delete __getattr__
117
+ setattr(self.obj, self.name, self.old) if self.old else delattr(self.obj, self.name)
118
+ Patch.applied_patches.remove(self)
119
+ self.is_applied = False
120
+
121
+ def __enter__(self):
122
+ self.apply()
123
+ return self
124
+
125
+ def __exit__(self, exc_type, exc_val, exc_tb):
126
+ self.undo()
127
+ return self
128
+
129
+ @staticmethod
130
+ def extend_class(target: type, fn: Callable):
131
+ def _getattr(obj, name):
132
+ if name != fn.__name__:
133
+ raise AttributeError(f"`{target.__name__}` object has no attribute `{name}`")
134
+
135
+ return functools.partial(fn, obj)
136
+
137
+ return Patch(target, "__getattr__", _getattr)
138
+
139
+ @staticmethod
140
+ def function(target: Callable, fn: Callable, pass_target: bool = True):
141
+ obj = get_defining_object(target)
142
+ name = target.__name__
143
+
144
+ is_class_instance = not inspect.isclass(obj) and not inspect.ismodule(obj)
145
+ if is_class_instance:
146
+ # special case: If the defining object is not a class, but a class instance,
147
+ # then we need to bind the patch function to the target object. Also, we need
148
+ # to ensure that the final patched method has the same name as the original
149
+ # method on the defining object (required for restoring objects with patched
150
+ # methods from persistence, to avoid AttributeError).
151
+ fn.__name__ = name
152
+ fn = types.MethodType(fn, obj)
153
+
154
+ if pass_target:
155
+ new = create_patch_proxy(target, fn)
156
+ else:
157
+ new = fn
158
+
159
+ return Patch(obj, name, new)
160
+
161
+ def __str__(self):
162
+ try:
163
+ # try to unwrap the original underlying function that is used as patch (basically undoes what
164
+ # ``create_patch_proxy`` does)
165
+ new = self.new.__subject__
166
+ except AttributeError:
167
+ new = self.new
168
+
169
+ old = self.old
170
+ return f"Patch({to_metadata_string(old)} -> {to_metadata_string(new)}, applied={self.is_applied})"
171
+
172
+
173
+ class Patches:
174
+ patches: list[Patch]
175
+
176
+ def __init__(self, patches: list[Patch] = None) -> None:
177
+ super().__init__()
178
+
179
+ self.patches = []
180
+ if patches:
181
+ self.patches.extend(patches)
182
+
183
+ def apply(self):
184
+ for p in self.patches:
185
+ p.apply()
186
+
187
+ def undo(self):
188
+ for p in self.patches:
189
+ p.undo()
190
+
191
+ def __enter__(self):
192
+ self.apply()
193
+ return self
194
+
195
+ def __exit__(self, exc_type, exc_val, exc_tb):
196
+ self.undo()
197
+
198
+ def add(self, patch: Patch):
199
+ self.patches.append(patch)
200
+
201
+ def function(self, target: Callable, fn: Callable, pass_target: bool = True):
202
+ self.add(Patch.function(target, fn, pass_target))
203
+
204
+
205
+ def patch(target, pass_target=True):
206
+ """
207
+ Function decorator to create a patch via Patch.function and immediately apply it.
208
+
209
+ Example::
210
+
211
+ def echo(string):
212
+ return "echo " + string
213
+
214
+ @patch(target=echo)
215
+ def echo_uppercase(target, string):
216
+ return target(string).upper()
217
+
218
+ echo("foo")
219
+ # will print "ECHO FOO"
220
+
221
+ echo_uppercase.patch.undo()
222
+ echo("foo")
223
+ # will print "echo foo"
224
+
225
+ When you are patching classes, with ``pass_target=True``, the unbound function will be passed as the first
226
+ argument before ``self``.
227
+
228
+ For example::
229
+
230
+ @patch(target=MyEchoer.do_echo, pass_target=True)
231
+ def my_patch(fn, self, *args):
232
+ return fn(self, *args)
233
+
234
+ @patch(target=MyEchoer.do_echo, pass_target=False)
235
+ def my_patch(self, *args):
236
+ ...
237
+
238
+ This decorator can also patch a class type with a new method.
239
+
240
+ For example:
241
+ @patch(target=MyEchoer)
242
+ def new_echo(self, *args):
243
+ ...
244
+
245
+ :param target: the function or method to patch
246
+ :param pass_target: whether to pass the target to the patching function as first parameter
247
+ :returns: the same function, but with a patch created
248
+ """
249
+
250
+ @functools.wraps(target)
251
+ def wrapper(fn):
252
+ fn.patch = (
253
+ Patch.extend_class(target, fn)
254
+ if inspect.isclass(target)
255
+ else Patch.function(target, fn, pass_target=pass_target)
256
+ )
257
+ fn.patch.apply()
258
+ return fn
259
+
260
+ return wrapper
@@ -0,0 +1,77 @@
1
+ import platform
2
+ from functools import lru_cache
3
+
4
+
5
+ def is_mac_os() -> bool:
6
+ return "darwin" == platform.system().lower()
7
+
8
+
9
+ def is_linux() -> bool:
10
+ return "linux" == platform.system().lower()
11
+
12
+
13
+ def is_windows() -> bool:
14
+ return "windows" == platform.system().lower()
15
+
16
+
17
+ @lru_cache
18
+ def is_debian() -> bool:
19
+ from localstack_cli.utils.files import load_file
20
+
21
+ return "Debian" in load_file("/etc/issue", "")
22
+
23
+
24
+ @lru_cache
25
+ def is_redhat() -> bool:
26
+ from localstack_cli.utils.files import load_file
27
+
28
+ return "rhel" in load_file("/etc/os-release", "")
29
+
30
+
31
+ class Arch(str):
32
+ """LocalStack standardised machine architecture names"""
33
+
34
+ amd64 = "amd64"
35
+ arm64 = "arm64"
36
+
37
+
38
+ def standardized_arch(arch: str):
39
+ """
40
+ Returns LocalStack standardised machine architecture name.
41
+ """
42
+ if arch == "x86_64":
43
+ return Arch.amd64
44
+ if arch == "aarch64":
45
+ return Arch.arm64
46
+ return arch
47
+
48
+
49
+ def get_arch() -> str:
50
+ """
51
+ Returns the current machine architecture.
52
+ """
53
+ arch = platform.machine()
54
+ return standardized_arch(arch)
55
+
56
+
57
+ # TODO: implement proper architecture detection (e.g., test whether an architecture-specific binary actually runs)
58
+ # because this naive implementation does not cover cross-architecture emulation
59
+ def is_arm_compatible() -> bool:
60
+ """Returns true if the current machine is compatible with ARM instructions and false otherwise."""
61
+ return get_arch() == Arch.arm64
62
+
63
+
64
+ def get_os() -> str:
65
+ if is_mac_os():
66
+ return "osx"
67
+ if is_linux():
68
+ return "linux"
69
+ if is_windows():
70
+ return "windows"
71
+ raise ValueError("Unable to determine local operating system")
72
+
73
+
74
+ def in_docker() -> bool:
75
+ from localstack_cli import config
76
+
77
+ return config.in_docker()