dt-extensions-sdk 1.7.3__py3-none-any.whl → 1.8.0__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.
- {dt_extensions_sdk-1.7.3.dist-info → dt_extensions_sdk-1.8.0.dist-info}/METADATA +6 -5
- {dt_extensions_sdk-1.7.3.dist-info → dt_extensions_sdk-1.8.0.dist-info}/RECORD +9 -9
- {dt_extensions_sdk-1.7.3.dist-info → dt_extensions_sdk-1.8.0.dist-info}/WHEEL +1 -1
- dynatrace_extension/__about__.py +1 -1
- dynatrace_extension/cli/main.py +73 -15
- dynatrace_extension/sdk/callback.py +28 -18
- dynatrace_extension/sdk/extension.py +6 -1
- {dt_extensions_sdk-1.7.3.dist-info → dt_extensions_sdk-1.8.0.dist-info}/entry_points.txt +0 -0
- {dt_extensions_sdk-1.7.3.dist-info → dt_extensions_sdk-1.8.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dt-extensions-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Project-URL: Documentation, https://github.com/dynatrace-extensions/dt-extensions-python-sdk#readme
|
|
5
5
|
Project-URL: Issues, https://github.com/dynatrace-extensions/dt-extensions-python-sdk/issues
|
|
6
6
|
Project-URL: Source, https://github.com/dynatrace-extensions/dt-extensions-python-sdk
|
|
@@ -10,20 +10,21 @@ License-File: LICENSE.txt
|
|
|
10
10
|
Classifier: Development Status :: 4 - Beta
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
14
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
14
15
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
-
Requires-Python: <3.
|
|
16
|
+
Requires-Python: <3.15,>=3.10
|
|
16
17
|
Provides-Extra: cli
|
|
17
18
|
Requires-Dist: dt-cli>=1.6.13; extra == 'cli'
|
|
18
19
|
Requires-Dist: pyyaml; extra == 'cli'
|
|
19
20
|
Requires-Dist: ruff; extra == 'cli'
|
|
20
|
-
Requires-Dist: typer
|
|
21
|
+
Requires-Dist: typer; extra == 'cli'
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
|
|
23
24
|
# Dynatrace Extensions Python SDK
|
|
24
25
|
|
|
25
26
|
[](https://pypi.org/project/dt-extensions-sdk)
|
|
26
|
-
[](https://img.shields.io/badge/python-3.10-blue)
|
|
27
|
+
[](https://img.shields.io/badge/python-3.10%20%7C%203.14-blue)
|
|
27
28
|
|
|
28
29
|
-----
|
|
29
30
|
|
|
@@ -41,7 +42,7 @@ The documentation can be found on [github pages](https://dynatrace-extensions.gi
|
|
|
41
42
|
|
|
42
43
|
### Requirements:
|
|
43
44
|
|
|
44
|
-
* Python 3.10
|
|
45
|
+
* Python 3.10 or 3.14
|
|
45
46
|
|
|
46
47
|
### Install the SDK
|
|
47
48
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
dynatrace_extension/__about__.py,sha256
|
|
1
|
+
dynatrace_extension/__about__.py,sha256=feKH4_h_P2KAMzfeJfsy34a-VQCLlkBU5KBdHgyBGc4,110
|
|
2
2
|
dynatrace_extension/__init__.py,sha256=MJNJYCFWLEwPmBLoETWFZddyUCMDgZfKkRycmmGM_w4,806
|
|
3
3
|
dynatrace_extension/cli/__init__.py,sha256=HCboY_eJPoqjFmoPDsBL8Jk6aNvank8K7JpkVrgwzUM,123
|
|
4
|
-
dynatrace_extension/cli/main.py,sha256=
|
|
4
|
+
dynatrace_extension/cli/main.py,sha256=vQrYd-k_82Dklo2ult-10nkM8IhFO4xRACyBCVWOnSE,22646
|
|
5
5
|
dynatrace_extension/cli/schema.py,sha256=d8wKUodRiaU3hfSZDWVNpD15lBfhmif2oQ-k07IxcaA,3230
|
|
6
6
|
dynatrace_extension/cli/create/__init__.py,sha256=NfyOJCVlxs8dYtfDAMHS1Q5SJTuZcFzOg5rtaI-ZPRE,72
|
|
7
7
|
dynatrace_extension/cli/create/create.py,sha256=IcgyVcjud1QTUE99KNHVNTH7G0qQsQyldVchp6CG3v8,2695
|
|
@@ -17,10 +17,10 @@ dynatrace_extension/cli/create/extension_template/extension_name/__init__.py.tem
|
|
|
17
17
|
dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template,sha256=cS79GVxJB-V-gocu4ZOjmZ54HXJNg89eXdLf89zDHJQ,1249
|
|
18
18
|
dynatrace_extension/sdk/__init__.py,sha256=RsqQ1heGyCmSK3fhuEKAcxQIRCg4gEK0-eSkIehL5Nc,86
|
|
19
19
|
dynatrace_extension/sdk/activation.py,sha256=KIoPWMZs3tKiMG8XhCfeNgRlz2vxDKcAASgSACcEfIQ,1456
|
|
20
|
-
dynatrace_extension/sdk/callback.py,sha256=
|
|
20
|
+
dynatrace_extension/sdk/callback.py,sha256=S_ELy2D9Y2YhUsL2lBry3UZfmy3BrZgZM2hvKgQIQss,6949
|
|
21
21
|
dynatrace_extension/sdk/communication.py,sha256=uTSURmgSHit2N1hHUc3-yKmEBVMHi6hDBrdb1EaCAsE,18419
|
|
22
22
|
dynatrace_extension/sdk/event.py,sha256=J261imbFKpxfuAQ6Nfu3RRcsIQKKivy6fme1nww2g-8,388
|
|
23
|
-
dynatrace_extension/sdk/extension.py,sha256=
|
|
23
|
+
dynatrace_extension/sdk/extension.py,sha256=ZbuoHQ-sbj4jUSz0w1Iz6IePrnC9a0BEwoXIdLKJsU8,49957
|
|
24
24
|
dynatrace_extension/sdk/helper.py,sha256=m4gGHtIKYkfANC2MOGdxKUZlmH5tnZO6WTNqll27lyY,6476
|
|
25
25
|
dynatrace_extension/sdk/metric.py,sha256=-kq7JWpk7UGvcjqafTt-o6k4urwhsGVXmnuQg7Sf9PQ,3622
|
|
26
26
|
dynatrace_extension/sdk/runtime.py,sha256=7bC4gUJsVSHuL_E7r2EWrne95nm1BjZiMGkyNqA7ZCU,2796
|
|
@@ -31,8 +31,8 @@ dynatrace_extension/sdk/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
31
31
|
dynatrace_extension/sdk/vendor/mureq/LICENSE,sha256=8AVcgZgiT_mvK1fOofXtRRr2f1dRXS_K21NuxQgP4VM,671
|
|
32
32
|
dynatrace_extension/sdk/vendor/mureq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
dynatrace_extension/sdk/vendor/mureq/mureq.py,sha256=znF4mvzk5L03CLNozRz8UpK-fMijmSkObDFwlbhwLUg,14656
|
|
34
|
-
dt_extensions_sdk-1.
|
|
35
|
-
dt_extensions_sdk-1.
|
|
36
|
-
dt_extensions_sdk-1.
|
|
37
|
-
dt_extensions_sdk-1.
|
|
38
|
-
dt_extensions_sdk-1.
|
|
34
|
+
dt_extensions_sdk-1.8.0.dist-info/METADATA,sha256=gg6h4u5Vd29Z7GHQ6wTaSDCh5cuky1NLsvGTyeP8rg0,2801
|
|
35
|
+
dt_extensions_sdk-1.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
dt_extensions_sdk-1.8.0.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
|
|
37
|
+
dt_extensions_sdk-1.8.0.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
|
|
38
|
+
dt_extensions_sdk-1.8.0.dist-info/RECORD,,
|
dynatrace_extension/__about__.py
CHANGED
dynatrace_extension/cli/main.py
CHANGED
|
@@ -17,10 +17,8 @@ from .schema import ExtensionYaml
|
|
|
17
17
|
app = typer.Typer(pretty_exceptions_show_locals=False, pretty_exceptions_enable=False)
|
|
18
18
|
console = Console()
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
console.print(f"Python 3.10.X is required to build extensions, you are using {sys.version_info}", style="bold red")
|
|
23
|
-
sys.exit(1)
|
|
20
|
+
SUPPORTED_PYTHON_VERSIONS = ["3.10", "3.14"]
|
|
21
|
+
|
|
24
22
|
|
|
25
23
|
CERT_DIR_ENVIRONMENT_VAR = "DT_CERTIFICATES_FOLDER"
|
|
26
24
|
CERTIFICATE_DEFAULT_PATH = Path.home() / ".dynatrace" / "certificates"
|
|
@@ -106,6 +104,12 @@ def build(
|
|
|
106
104
|
"-o",
|
|
107
105
|
help="Only build for the extra platforms, useful when building from arm64 (mac)",
|
|
108
106
|
),
|
|
107
|
+
python_versions: list[str] | None = typer.Option(
|
|
108
|
+
None,
|
|
109
|
+
"--python-version",
|
|
110
|
+
"-p",
|
|
111
|
+
help=f"Python versions to download wheels for. Supported: {', '.join(SUPPORTED_PYTHON_VERSIONS)}",
|
|
112
|
+
),
|
|
109
113
|
):
|
|
110
114
|
"""
|
|
111
115
|
Builds and signs an extension using the developer fused key-certificate
|
|
@@ -119,6 +123,7 @@ def build(
|
|
|
119
123
|
:param extra_index_url: Extra index url to use when downloading dependencies
|
|
120
124
|
:param find_links: Extra index url to use when downloading dependencies
|
|
121
125
|
:param only_extra_platforms: If true, only build for the extra platforms, useful when building from arm64
|
|
126
|
+
:param python_versions: Python versions to download wheels for, defaults to ['3.10']
|
|
122
127
|
"""
|
|
123
128
|
console.print(f"Building and signing extension from {extension_dir} to {target_directory}", style="cyan")
|
|
124
129
|
if target_directory is None:
|
|
@@ -127,7 +132,7 @@ def build(
|
|
|
127
132
|
target_directory.mkdir()
|
|
128
133
|
|
|
129
134
|
console.print("Stage 1 - Download and build dependencies", style="bold blue")
|
|
130
|
-
wheel(extension_dir, extra_platforms, extra_index_url, find_links, only_extra_platforms)
|
|
135
|
+
wheel(extension_dir, extra_platforms, extra_index_url, find_links, only_extra_platforms, python_versions)
|
|
131
136
|
|
|
132
137
|
console.print("Stage 2 - Create the extension zip file", style="bold blue")
|
|
133
138
|
built_zip = assemble(extension_dir, target_directory)
|
|
@@ -184,6 +189,11 @@ def assemble(
|
|
|
184
189
|
return output
|
|
185
190
|
|
|
186
191
|
|
|
192
|
+
def _version_to_pip_version(version: str) -> str:
|
|
193
|
+
"""Convert a version string like '3.10' to pip format '310'."""
|
|
194
|
+
return version.replace(".", "")
|
|
195
|
+
|
|
196
|
+
|
|
187
197
|
@app.command(help="Downloads the dependencies of the extension to the lib folder")
|
|
188
198
|
def wheel(
|
|
189
199
|
extension_dir: Path = typer.Argument(".", help="Path to the python extension"),
|
|
@@ -202,6 +212,12 @@ def wheel(
|
|
|
202
212
|
"-o",
|
|
203
213
|
help="Only build for the extra platforms, useful when building from arm64 (mac)",
|
|
204
214
|
),
|
|
215
|
+
python_versions: list[str] | None = typer.Option(
|
|
216
|
+
None,
|
|
217
|
+
"--python-version",
|
|
218
|
+
"-p",
|
|
219
|
+
help=f"Python versions to download wheels for. Supported: {', '.join(SUPPORTED_PYTHON_VERSIONS)}",
|
|
220
|
+
),
|
|
205
221
|
):
|
|
206
222
|
"""
|
|
207
223
|
Builds the extension and it's dependencies into wheel files
|
|
@@ -212,27 +228,43 @@ def wheel(
|
|
|
212
228
|
:param extra_index_url: Extra index url to use when downloading dependencies
|
|
213
229
|
:param find_links: Extra index url to use when downloading dependencies
|
|
214
230
|
:param only_extra_platforms: If true, only build for the extra platforms, useful when building from arm64
|
|
231
|
+
:param python_versions: Python versions to download wheels for, defaults to ['3.10']
|
|
215
232
|
"""
|
|
233
|
+
# Handle OptionInfo objects when called directly (not via CLI)
|
|
234
|
+
if python_versions is None or isinstance(python_versions, typer.models.OptionInfo):
|
|
235
|
+
python_versions = ["3.10"]
|
|
236
|
+
if only_extra_platforms is None or isinstance(only_extra_platforms, typer.models.OptionInfo):
|
|
237
|
+
only_extra_platforms = False
|
|
238
|
+
|
|
239
|
+
# Validate python versions
|
|
240
|
+
for version in python_versions:
|
|
241
|
+
if version not in SUPPORTED_PYTHON_VERSIONS:
|
|
242
|
+
msg = (
|
|
243
|
+
f"Python version {version} is not supported. Supported versions: {', '.join(SUPPORTED_PYTHON_VERSIONS)}"
|
|
244
|
+
)
|
|
245
|
+
console.print(msg, style="bold red")
|
|
246
|
+
raise typer.Exit(1)
|
|
247
|
+
|
|
216
248
|
relative_lib_folder_dir = "extension/lib"
|
|
217
249
|
lib_folder: Path = extension_dir / relative_lib_folder_dir
|
|
218
250
|
_clean_directory(lib_folder)
|
|
219
251
|
|
|
220
252
|
console.print(f"Downloading dependencies to {lib_folder}", style="cyan")
|
|
221
253
|
|
|
222
|
-
#
|
|
223
|
-
command = [sys.executable, "-m", "pip", "wheel", "-w", relative_lib_folder_dir]
|
|
254
|
+
# Build the wheel for the extension itself (no deps)
|
|
255
|
+
command = [sys.executable, "-m", "pip", "wheel", "-w", relative_lib_folder_dir, "--no-deps"]
|
|
224
256
|
if extra_index_url is not None:
|
|
225
257
|
command.extend(["--extra-index-url", extra_index_url])
|
|
226
258
|
if find_links is not None:
|
|
227
259
|
command.extend(["--find-links", find_links])
|
|
228
|
-
if only_extra_platforms:
|
|
229
|
-
command.append("--no-deps")
|
|
230
260
|
command.append(".")
|
|
231
261
|
run_process(command, cwd=extension_dir)
|
|
232
262
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
263
|
+
# Download dependencies for the current platform for each requested python version
|
|
264
|
+
if not only_extra_platforms:
|
|
265
|
+
for version in python_versions:
|
|
266
|
+
pip_version = _version_to_pip_version(version)
|
|
267
|
+
console.print(f"Downloading wheels for Python {version} (current platform)", style="cyan")
|
|
236
268
|
command = [
|
|
237
269
|
sys.executable,
|
|
238
270
|
"-m",
|
|
@@ -241,17 +273,43 @@ def wheel(
|
|
|
241
273
|
"-d",
|
|
242
274
|
relative_lib_folder_dir,
|
|
243
275
|
"--only-binary=:all:",
|
|
244
|
-
"--
|
|
245
|
-
|
|
276
|
+
"--python-version",
|
|
277
|
+
pip_version,
|
|
246
278
|
]
|
|
247
279
|
if extra_index_url:
|
|
248
280
|
command.extend(["--extra-index-url", extra_index_url])
|
|
249
281
|
if find_links:
|
|
250
282
|
command.extend(["--find-links", find_links])
|
|
251
283
|
command.append(".")
|
|
252
|
-
|
|
253
284
|
run_process(command, cwd=extension_dir)
|
|
254
285
|
|
|
286
|
+
# Download dependencies for extra platforms for each requested python version
|
|
287
|
+
if extra_platforms:
|
|
288
|
+
for extra_platform in extra_platforms:
|
|
289
|
+
for version in python_versions:
|
|
290
|
+
pip_version = _version_to_pip_version(version)
|
|
291
|
+
console.print(f"Downloading wheels for Python {version}, platform {extra_platform}", style="cyan")
|
|
292
|
+
command = [
|
|
293
|
+
sys.executable,
|
|
294
|
+
"-m",
|
|
295
|
+
"pip",
|
|
296
|
+
"download",
|
|
297
|
+
"-d",
|
|
298
|
+
relative_lib_folder_dir,
|
|
299
|
+
"--only-binary=:all:",
|
|
300
|
+
"--python-version",
|
|
301
|
+
pip_version,
|
|
302
|
+
"--platform",
|
|
303
|
+
extra_platform,
|
|
304
|
+
]
|
|
305
|
+
if extra_index_url:
|
|
306
|
+
command.extend(["--extra-index-url", extra_index_url])
|
|
307
|
+
if find_links:
|
|
308
|
+
command.extend(["--find-links", find_links])
|
|
309
|
+
command.append(".")
|
|
310
|
+
|
|
311
|
+
run_process(command, cwd=extension_dir)
|
|
312
|
+
|
|
255
313
|
console.print(f"Installed dependencies to {lib_folder}", style="bold green")
|
|
256
314
|
|
|
257
315
|
|
|
@@ -23,6 +23,7 @@ class WrappedCallback:
|
|
|
23
23
|
kwargs: dict | None = None,
|
|
24
24
|
running_in_sim=False,
|
|
25
25
|
activation_type: ActivationType | None = None,
|
|
26
|
+
offset_seconds: float | None = None,
|
|
26
27
|
):
|
|
27
28
|
self.callback: Callable = callback
|
|
28
29
|
if args is None:
|
|
@@ -48,6 +49,7 @@ class WrappedCallback:
|
|
|
48
49
|
self.timeouts_count = 0 # counter per interval = 1 min by default
|
|
49
50
|
self.exception_count = 0 # counter per interval = 1 min by default
|
|
50
51
|
self.iterations = 0 # how many times we ran the callback iterator for this callback
|
|
52
|
+
self.offset_seconds = offset_seconds or self.calculate_initial_wait_time()
|
|
51
53
|
|
|
52
54
|
def get_current_time_with_cluster_diff(self):
|
|
53
55
|
return datetime.now() + timedelta(milliseconds=self.cluster_time_diff)
|
|
@@ -97,24 +99,28 @@ class WrappedCallback:
|
|
|
97
99
|
def name(self):
|
|
98
100
|
return self.callback.__name__
|
|
99
101
|
|
|
102
|
+
def calculate_initial_wait_time(self) -> float:
|
|
103
|
+
"""
|
|
104
|
+
Here we chose a random second between 5 and 55 to start the callback
|
|
105
|
+
This is to distribute load for extension running on this host
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
now = self.get_current_time_with_cluster_diff()
|
|
109
|
+
random_second = random.randint(5, 55) # noqa: S311
|
|
110
|
+
next_execution = datetime.now().replace(second=random_second, microsecond=0)
|
|
111
|
+
if next_execution <= now:
|
|
112
|
+
# The random chosen second already passed this minute
|
|
113
|
+
next_execution += timedelta(minutes=1)
|
|
114
|
+
wait_time = (next_execution - now).total_seconds()
|
|
115
|
+
self.logger.debug(f"Randomly choosing next execution time for callback {self} to be {next_execution}")
|
|
116
|
+
return wait_time
|
|
117
|
+
|
|
100
118
|
def initial_wait_time(self) -> float:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
now = self.get_current_time_with_cluster_diff()
|
|
109
|
-
random_second = random.randint(5, 55) # noqa: S311
|
|
110
|
-
next_execution = datetime.now().replace(second=random_second, microsecond=0)
|
|
111
|
-
if next_execution <= now:
|
|
112
|
-
# The random chosen second already passed this minute
|
|
113
|
-
next_execution += timedelta(minutes=1)
|
|
114
|
-
wait_time = (next_execution - now).total_seconds()
|
|
115
|
-
self.logger.debug(f"Randomly choosing next execution time for callback {self} to be {next_execution}")
|
|
116
|
-
return wait_time
|
|
117
|
-
return 0
|
|
119
|
+
# When running from the simulator, we don't want any offset
|
|
120
|
+
if self.running_in_sim:
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
return self.offset_seconds
|
|
118
124
|
|
|
119
125
|
def get_adjusted_metric_timestamp(self) -> datetime:
|
|
120
126
|
"""
|
|
@@ -151,4 +157,8 @@ class WrappedCallback:
|
|
|
151
157
|
This is done using execution total, the interval and the start timestamp
|
|
152
158
|
:return: float
|
|
153
159
|
"""
|
|
154
|
-
return
|
|
160
|
+
return (
|
|
161
|
+
self.initial_wait_time()
|
|
162
|
+
+ self.start_timestamp_monotonic
|
|
163
|
+
+ self.interval.total_seconds() * (self.iterations or 1)
|
|
164
|
+
)
|
|
@@ -385,6 +385,7 @@ class Extension:
|
|
|
385
385
|
interval: timedelta | int,
|
|
386
386
|
args: tuple | None = None,
|
|
387
387
|
activation_type: ActivationType | None = None,
|
|
388
|
+
offset_seconds: float | None = None,
|
|
388
389
|
) -> None:
|
|
389
390
|
"""Schedule a method to be executed periodically.
|
|
390
391
|
|
|
@@ -398,6 +399,7 @@ class Extension:
|
|
|
398
399
|
args: Arguments to the callback, if any
|
|
399
400
|
activation_type: Optional activation type when this callback should run,
|
|
400
401
|
can be 'ActivationType.LOCAL' or 'ActivationType.REMOTE'
|
|
402
|
+
offset_seconds: Optional offset of first execution represented in seconds. Offset is random if `offset_seconds` is `None`.
|
|
401
403
|
"""
|
|
402
404
|
|
|
403
405
|
if isinstance(interval, int):
|
|
@@ -407,7 +409,10 @@ class Extension:
|
|
|
407
409
|
msg = f"Interval must be at least 1 second, got {interval.total_seconds()} seconds"
|
|
408
410
|
raise ValueError(msg)
|
|
409
411
|
|
|
410
|
-
callback = WrappedCallback(
|
|
412
|
+
callback = WrappedCallback(
|
|
413
|
+
interval, callback, api_logger, args, activation_type=activation_type, offset_seconds=offset_seconds
|
|
414
|
+
)
|
|
415
|
+
|
|
411
416
|
if self._is_fastcheck:
|
|
412
417
|
self._scheduled_callbacks_before_run.append(callback)
|
|
413
418
|
else:
|
|
File without changes
|
{dt_extensions_sdk-1.7.3.dist-info → dt_extensions_sdk-1.8.0.dist-info}/licenses/LICENSE.txt
RENAMED
|
File without changes
|