clams-python 1.8.0__tar.gz → 1.8.2__tar.gz
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.
- {clams_python-1.8.0 → clams_python-1.8.2}/CHANGELOG.md +30 -0
- {clams_python-1.8.0/clams_python.egg-info → clams_python-1.8.2}/PKG-INFO +2 -2
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/app/__init__.py +50 -71
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/__init__.py +18 -3
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/utl-tf/timeframe.py.template +8 -0
- {clams_python-1.8.0 → clams_python-1.8.2/clams_python.egg-info}/PKG-INFO +2 -2
- {clams_python-1.8.0 → clams_python-1.8.2}/clams_python.egg-info/requires.txt +1 -1
- clams_python-1.8.2/clams_python.egg-info/scm_version.json +8 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/target-versions.csv +2 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/pyproject.toml +1 -1
- {clams_python-1.8.0 → clams_python-1.8.2}/tests/test_clamsapp.py +162 -14
- {clams_python-1.8.0 → clams_python-1.8.2}/tests/test_develop.py +15 -1
- clams_python-1.8.0/clams_python.egg-info/scm_version.json +0 -8
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/codecov.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/container.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/containers.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/issue-assign.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/issue-close.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/issue-infra-project.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/publish.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.github/workflows/release-check.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/.gitignore +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/CONTRIBUTING.md +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/LICENSE +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/README.md +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/build-tools/build.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/build-tools/clean.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/build-tools/docs.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/build-tools/prep_release.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/build-tools/publish.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/build-tools/test.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/appmetadata/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/backends/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/backends/hf.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/.dockerignore.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/.gitignore.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/Containerfile.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/LICENSE.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/README.md.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/app.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/cli.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/metadata.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/requirements.txt.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/utl-tf/__init__.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/envelop/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/restify/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/serve/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams/ver/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams_python.egg-info/SOURCES.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams_python.egg-info/dependency_links.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams_python.egg-info/entry_points.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams_python.egg-info/scm_file_list.json +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/clams_python.egg-info/top_level.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/Containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/ffmpeg-hf.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/ffmpeg-hf4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/ffmpeg-hf5.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/ffmpeg-tf2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/ffmpeg-torch2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/ffmpeg.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/generate_containers_yml.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/hf.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/hf4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/hf5.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/jdk8.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/opencv4-hf.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/opencv4-hf4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/opencv4-hf5.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/opencv4-tf2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/opencv4-torch2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/opencv4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/tf2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/container/torch2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/app-baseclasses.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/appdirectory.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/appmetadata.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/autodoc/clams.app.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/autodoc/clams.appmetadata.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/autodoc/clams.backends.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/autodoc/clams.mmif_utils.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/autodoc/clams.restify.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/clamsapp.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/cli.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/conf.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/gpu-apps.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/index.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/input-output.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/introduction.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/modules.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/runtime-params.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/documentation/tutorial.md +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/setup.cfg +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/tests/metadata.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/tests/pillow-logo.png +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/tests/test_backends_hf.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/tests/test_envelope.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.2}/tests/test_promptable.py +0 -0
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
|
|
2
|
+
## releasing 1.8.1 (2026-10-27)
|
|
3
|
+
### Overview
|
|
4
|
+
|
|
5
|
+
Updates `mmif-python` to 1.6.0, with many small fixes for the consistency of MMIF files and CLAMS workflows.
|
|
6
|
+
|
|
7
|
+
### Changes
|
|
8
|
+
|
|
9
|
+
* `mmif-python` is updated to 1.6.0 (https://github.com/clamsproject/clams-python/issues/314)
|
|
10
|
+
* view timestamps: consistently in UTC now, instead of local time (https://github.com/clamsproject/clams-python/issues/312)
|
|
11
|
+
* error views: fixed a bug where an error in a run overwrote the view of an earlier run of the same app (part of https://github.com/clamsproject/clams-python/pull/313, found during working on #312)
|
|
12
|
+
* document properties: fixed a bug where the (capital) `Annotation` for properties added to an existing document was attached to a wrong view, when the app produced no other annotations (https://github.com/clamsproject/clams-python/issues/314)
|
|
13
|
+
* `clams develop`: the `utl-tf` recipe is deprecated and no longer baked by default; its helpers are now provided by mmif-python 1.6.0 as `Mmif.get_annotations_of_labels()` and `mmif.utils.video_document_helper.iter_timeframe_images()` (https://github.com/clamsproject/clams-python/issues/314)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## releasing 1.8.0 (2026-10-06)
|
|
17
|
+
### Overview
|
|
18
|
+
Adds runtime parameters that cap the number of images per prompt in promptable apps, and updates `mmif-python` to 1.5.4.
|
|
19
|
+
|
|
20
|
+
### Additions
|
|
21
|
+
|
|
22
|
+
* `maxImagesPerPrompt` and `imagesPerPromptMode`: new runtime parameters for promptable apps. A TimeFrame with more images than the cap is split into several prompts, which bounds GPU memory for long scenes ( https://github.com/clamsproject/clams-python/issues/306).
|
|
23
|
+
|
|
24
|
+
### Changes
|
|
25
|
+
|
|
26
|
+
* `mmif-python` is updated to 1.5.4, which fixes image extraction from truncated or otherwise incomplete video files.
|
|
27
|
+
* (for contributors) Test-time type checking moved from `pytype` to `mypy` (https://github.com/clamsproject/clams-python/issues/285).
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
2
32
|
## releasing 1.7.4 (2026-09-01)
|
|
3
33
|
### Overview
|
|
4
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clams-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
4
4
|
Summary: A collection of APIs to develop CLAMS app for python
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: homepage, https://clams.ai
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
13
13
|
Requires-Python: >=3.10
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: mmif-python==1.
|
|
16
|
+
Requires-Dist: mmif-python==1.7.0
|
|
17
17
|
Requires-Dist: Flask>=2
|
|
18
18
|
Requires-Dist: Flask-RESTful>=0.3.9
|
|
19
19
|
Requires-Dist: gunicorn>=20
|
|
@@ -17,7 +17,9 @@ __all__ = ['ClamsApp', 'ClamsPromptableApp', 'ClamsHFPromptableApp',
|
|
|
17
17
|
|
|
18
18
|
from typing import Union, Any, Optional, Dict, List, Mapping, Tuple, cast
|
|
19
19
|
|
|
20
|
-
from mmif import
|
|
20
|
+
from mmif import (
|
|
21
|
+
Mmif, Document, DocumentTypes, View, AnnotationTypes, EmptyOutputWarning,
|
|
22
|
+
)
|
|
21
23
|
from mmif.utils.video_document_helper import (
|
|
22
24
|
SamplingMode, SAMPLING_MODE_DESCRIPTIONS, SAMPLING_MODE_DEFAULT,
|
|
23
25
|
_sampling_mode,
|
|
@@ -53,10 +55,6 @@ falsy_values = [
|
|
|
53
55
|
]
|
|
54
56
|
|
|
55
57
|
|
|
56
|
-
class EmptyOutputWarning(UserWarning):
|
|
57
|
-
"""Raised when an app's ``_annotate()`` produces no non-empty views."""
|
|
58
|
-
|
|
59
|
-
|
|
60
58
|
class ImagesPerPromptMode(Enum):
|
|
61
59
|
"""
|
|
62
60
|
How :py:meth:`ClamsPromptableApp.chunk_tasks` sizes the chunks of a
|
|
@@ -224,7 +222,6 @@ class ClamsApp(ABC):
|
|
|
224
222
|
if not isinstance(mmif, Mmif):
|
|
225
223
|
mmif, runtime_params = unwrap_if_envelope(mmif, runtime_params)
|
|
226
224
|
mmif = Mmif(mmif)
|
|
227
|
-
existing_view_ids = {view.id for view in mmif.views}
|
|
228
225
|
issued_warnings: List[Union[Warning, warnings.WarningMessage]] = []
|
|
229
226
|
for key in runtime_params:
|
|
230
227
|
if key not in self.annotate_param_spec:
|
|
@@ -238,59 +235,43 @@ class ClamsApp(ABC):
|
|
|
238
235
|
if sampling_mode_str is not None:
|
|
239
236
|
_sampling_mode.set(SamplingMode(sampling_mode_str))
|
|
240
237
|
t = datetime.now()
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
for gpu in subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total', '--format=csv,noheader'],
|
|
279
|
-
stdout=subprocess.PIPE).stdout.decode('utf-8').strip().split('\n'):
|
|
280
|
-
name, mem = gpu.split(', ')
|
|
281
|
-
runtime_recs['cuda'].append(self._cuda_device_name_concat(name, mem))
|
|
282
|
-
for annotated_view in annotated.views:
|
|
283
|
-
if annotated_view.id not in existing_view_ids and annotated_view.metadata.app == str(self.metadata.identifier):
|
|
284
|
-
annotated_view.metadata.timestamp = run_id
|
|
285
|
-
profiling_data: Dict[str, Any] = {}
|
|
286
|
-
if runningTime:
|
|
287
|
-
profiling_data['runningTime'] = str(td)
|
|
288
|
-
if len(runtime_recs) > 0:
|
|
289
|
-
profiling_data['hardware'] = runtime_recs
|
|
290
|
-
if profiling_data:
|
|
291
|
-
annotated_view.metadata.set_additional_property('appProfiling', profiling_data)
|
|
292
|
-
|
|
293
|
-
return annotated.serialize(pretty=pretty, sanitize=True)
|
|
238
|
+
signature = View()
|
|
239
|
+
self.sign_view(signature, refined)
|
|
240
|
+
with mmif.set_producer(signature) as producer:
|
|
241
|
+
with warnings.catch_warnings(record=True) as ws:
|
|
242
|
+
annotated, cuda_profiler = self._profile_cuda_memory(self._annotate)(mmif, **refined)
|
|
243
|
+
producer.add_warnings(*issued_warnings, *ws)
|
|
244
|
+
td = datetime.now() - t
|
|
245
|
+
runningTime = refined.get('runningTime', False)
|
|
246
|
+
hwFetch = refined.get('hwFetch', False)
|
|
247
|
+
runtime_recs: Dict[str, Any] = {}
|
|
248
|
+
if hwFetch:
|
|
249
|
+
import multiprocessing
|
|
250
|
+
import platform, shutil, subprocess
|
|
251
|
+
runtime_recs['cpu'] = f"{platform.machine()}, {multiprocessing.cpu_count()} cores"
|
|
252
|
+
runtime_recs['cuda'] = []
|
|
253
|
+
# Use cuda_profiler data if available, otherwise fallback to nvidia-smi
|
|
254
|
+
if cuda_profiler:
|
|
255
|
+
for gpu_name, mem_info in cuda_profiler.items():
|
|
256
|
+
total_str = self._cuda_memory_to_str(mem_info['total'])
|
|
257
|
+
available_str = self._cuda_memory_to_str(mem_info['available_before'])
|
|
258
|
+
peak_str = self._cuda_memory_to_str(mem_info['peak'])
|
|
259
|
+
runtime_recs['cuda'].append(
|
|
260
|
+
f"{gpu_name}, {total_str} total, {available_str} available, {peak_str} peak used"
|
|
261
|
+
)
|
|
262
|
+
elif shutil.which('nvidia-smi'):
|
|
263
|
+
for gpu in subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total', '--format=csv,noheader'],
|
|
264
|
+
stdout=subprocess.PIPE).stdout.decode('utf-8').strip().split('\n'):
|
|
265
|
+
name, mem = gpu.split(', ')
|
|
266
|
+
runtime_recs['cuda'].append(self._cuda_device_name_concat(name, mem))
|
|
267
|
+
profiling_data: Dict[str, Any] = {}
|
|
268
|
+
if runningTime:
|
|
269
|
+
profiling_data['runningTime'] = str(td)
|
|
270
|
+
if len(runtime_recs) > 0:
|
|
271
|
+
profiling_data['hardware'] = runtime_recs
|
|
272
|
+
if profiling_data:
|
|
273
|
+
producer.set_view_metadata('appProfiling', profiling_data)
|
|
274
|
+
return annotated.serialize(pretty=pretty, sanitize=True)
|
|
294
275
|
|
|
295
276
|
@abstractmethod
|
|
296
277
|
def _annotate(self, mmif: Mmif, _raw_parameters=None, **refined_parameters) -> Mmif:
|
|
@@ -391,7 +372,9 @@ class ClamsApp(ABC):
|
|
|
391
372
|
"""
|
|
392
373
|
view.metadata.app = str(self.metadata.identifier)
|
|
393
374
|
if self.metadata.app_tags:
|
|
394
|
-
|
|
375
|
+
# item assignment stores the tags in the declared `appTags` field
|
|
376
|
+
# when mmif-python has one, and as an additional property otherwise
|
|
377
|
+
view.metadata['appTags'] = list(self.metadata.app_tags)
|
|
395
378
|
if self._RAW_PARAMS_KEY in runtime_conf:
|
|
396
379
|
for k, v in runtime_conf.items():
|
|
397
380
|
if k == self._RAW_PARAMS_KEY:
|
|
@@ -436,9 +419,11 @@ class ClamsApp(ABC):
|
|
|
436
419
|
|
|
437
420
|
def set_error_view(self, mmif: Union[str, dict, Mmif], **runtime_conf: List[str]) -> Mmif:
|
|
438
421
|
"""
|
|
439
|
-
A method to record an error instead of annotation results in
|
|
440
|
-
|
|
441
|
-
|
|
422
|
+
A method to record an error instead of annotation results in a new
|
|
423
|
+
view appended to the input MMIF. Existing views are left untouched,
|
|
424
|
+
including views from earlier runs of the same app. For logging
|
|
425
|
+
purpose, the runtime parameters used when the error occurred must be
|
|
426
|
+
passed as well.
|
|
442
427
|
|
|
443
428
|
:param mmif: input MMIF object
|
|
444
429
|
:param runtime_conf: parameters passed to annotate when the app encountered the error
|
|
@@ -448,14 +433,8 @@ class ClamsApp(ABC):
|
|
|
448
433
|
if isinstance(mmif, (bytes, str, dict)):
|
|
449
434
|
mmif, runtime_conf = unwrap_if_envelope(mmif, runtime_conf)
|
|
450
435
|
mmif = Mmif(mmif)
|
|
451
|
-
error_view
|
|
452
|
-
|
|
453
|
-
if view.metadata.app == str(self.metadata.identifier):
|
|
454
|
-
error_view = view
|
|
455
|
-
break
|
|
456
|
-
if error_view is None:
|
|
457
|
-
error_view = mmif.new_view()
|
|
458
|
-
self.sign_view(error_view, runtime_conf)
|
|
436
|
+
error_view = mmif.new_view()
|
|
437
|
+
self.sign_view(error_view, runtime_conf)
|
|
459
438
|
exc_info = sys.exc_info()
|
|
460
439
|
error_view.set_error(f'{exc_info[0]}: {exc_info[1]}',
|
|
461
440
|
'\t\n'.join(traceback.format_tb(exc_info[2])))
|
|
@@ -2,12 +2,15 @@ import argparse
|
|
|
2
2
|
import pathlib
|
|
3
3
|
import re
|
|
4
4
|
import shutil
|
|
5
|
+
import warnings
|
|
5
6
|
from string import Template
|
|
6
7
|
from typing import List
|
|
7
8
|
|
|
8
9
|
import clams
|
|
9
10
|
|
|
10
11
|
update_tmp_suffix = '.tmp'
|
|
12
|
+
# Recipes with a ``deprecated`` message are left out of the default recipe
|
|
13
|
+
# set and warn when baked on request.
|
|
11
14
|
available_recipes = {
|
|
12
15
|
'app': {
|
|
13
16
|
'description': 'Skeleton code for a CLAMS app',
|
|
@@ -20,12 +23,18 @@ available_recipes = {
|
|
|
20
23
|
'targetdir': '.github',
|
|
21
24
|
},
|
|
22
25
|
'utl-tf': {
|
|
23
|
-
'description': 'Local helper module for iterating TimeFrames and collecting per-TF frame tasks '
|
|
26
|
+
'description': 'DEPRECATED. Local helper module for iterating TimeFrames and collecting per-TF frame tasks '
|
|
24
27
|
'(baked into ``utils/timeframe.py``; backend-agnostic, safe to edit/delete)',
|
|
25
28
|
'sourcedir': 'utl-tf',
|
|
26
29
|
'targetdir': 'utils',
|
|
30
|
+
'deprecated': 'The `utl-tf` recipe is deprecated and is no longer baked by default. mmif-python now '
|
|
31
|
+
'provides its functions: use `Mmif.get_annotations_of_labels()` to filter TimeFrames by '
|
|
32
|
+
'label, and `mmif.utils.video_document_helper.iter_timeframe_images()` to sample images '
|
|
33
|
+
'per TimeFrame with the TimePoint each image came from.',
|
|
27
34
|
},
|
|
28
35
|
}
|
|
36
|
+
default_recipes = [name for name, recipe in available_recipes.items()
|
|
37
|
+
if 'deprecated' not in recipe]
|
|
29
38
|
|
|
30
39
|
|
|
31
40
|
class CookieCutter(object):
|
|
@@ -37,7 +46,13 @@ class CookieCutter(object):
|
|
|
37
46
|
if recipes:
|
|
38
47
|
self.recipes = recipes
|
|
39
48
|
else:
|
|
40
|
-
self.recipes = list(
|
|
49
|
+
self.recipes = list(default_recipes)
|
|
50
|
+
for recipe in self.recipes:
|
|
51
|
+
message = available_recipes.get(recipe, {}).get('deprecated')
|
|
52
|
+
if message:
|
|
53
|
+
# FutureWarning, unlike DeprecationWarning, is shown to CLI
|
|
54
|
+
# users by default
|
|
55
|
+
warnings.warn(message, FutureWarning, stacklevel=2)
|
|
41
56
|
|
|
42
57
|
def tokenize_rawname(self):
|
|
43
58
|
word_pat = re.compile('[A-Z][a-z]+|[0-9A-Z]+(?=[A-Z][a-z])|[0-9A-Z]{2,}|[a-z0-9]{2,}|[a-zA-Z0-9]')
|
|
@@ -158,7 +173,7 @@ def prep_argparser(**kwargs):
|
|
|
158
173
|
action='store',
|
|
159
174
|
nargs='+',
|
|
160
175
|
default=[],
|
|
161
|
-
help=f"Pick recipes to bake. DEFAULT: {
|
|
176
|
+
help=f"Pick recipes to bake. DEFAULT: {default_recipes}"
|
|
162
177
|
)
|
|
163
178
|
parser.add_argument(
|
|
164
179
|
'-n', '--name',
|
{clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/utl-tf/timeframe.py.template
RENAMED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TimeFrame iteration / frame-sampling helpers, local to this app.
|
|
3
3
|
|
|
4
|
+
DEPRECATED: mmif-python now provides these functions. Use
|
|
5
|
+
``Mmif.get_annotations_of_labels()`` in place of
|
|
6
|
+
:func:`iter_timeframes`, and
|
|
7
|
+
``mmif.utils.video_document_helper.iter_timeframe_images()`` in place of
|
|
8
|
+
:func:`collect_timeframes_of_interest` (it yields
|
|
9
|
+
``(time_frame, images, timepoint_ids)``, and wrapping those into
|
|
10
|
+
``clams.app.PromptTask`` is left to the app).
|
|
11
|
+
|
|
4
12
|
Generated by ``clams develop -r utl-tf``. The code in this file is part
|
|
5
13
|
of your app, not the SDK; edit it freely, refactor as needed, or delete
|
|
6
14
|
the whole file if your app does not iterate TimeFrame annotations.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clams-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
4
4
|
Summary: A collection of APIs to develop CLAMS app for python
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: homepage, https://clams.ai
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
13
13
|
Requires-Python: >=3.10
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: mmif-python==1.
|
|
16
|
+
Requires-Dist: mmif-python==1.7.0
|
|
17
17
|
Requires-Dist: Flask>=2
|
|
18
18
|
Requires-Dist: Flask-RESTful>=0.3.9
|
|
19
19
|
Requires-Dist: gunicorn>=20
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"``clams-python`` version","``mmif-python`` version","Target MMIF Specification"
|
|
2
|
+
`1.8.2 <https://pypi.org/project/clams-python/1.8.2/>`__,`1.7.0 <https://pypi.org/project/mmif-python/1.7.0/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
3
|
+
`1.8.1 <https://pypi.org/project/clams-python/1.8.1/>`__,`1.6.0 <https://pypi.org/project/mmif-python/1.6.0/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
2
4
|
`1.8.0 <https://pypi.org/project/clams-python/1.8.0/>`__,`1.5.4 <https://pypi.org/project/mmif-python/1.5.4/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
3
5
|
`1.7.4 <https://pypi.org/project/clams-python/1.7.4/>`__,`1.5.3 <https://pypi.org/project/mmif-python/1.5.3/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
4
6
|
`1.7.3 <https://pypi.org/project/clams-python/1.7.3/>`__,`1.5.1 <https://pypi.org/project/mmif-python/1.5.1/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
@@ -5,11 +5,16 @@ import sys
|
|
|
5
5
|
import tempfile
|
|
6
6
|
import unittest
|
|
7
7
|
import warnings
|
|
8
|
+
from datetime import datetime, timezone
|
|
8
9
|
from typing import Union
|
|
10
|
+
from unittest.mock import patch
|
|
9
11
|
|
|
10
12
|
import jsonschema
|
|
11
13
|
import pytest
|
|
12
|
-
from mmif import
|
|
14
|
+
from mmif import (
|
|
15
|
+
Mmif, Document, DocumentTypes, AnnotationTypes, View, EmptyOutputWarning,
|
|
16
|
+
__specver__,
|
|
17
|
+
)
|
|
13
18
|
from mmif.serialize.mmif import ViewsList
|
|
14
19
|
|
|
15
20
|
import clams.app
|
|
@@ -334,11 +339,6 @@ class TestClamsApp(unittest.TestCase):
|
|
|
334
339
|
self.assertEqual(len(v.metadata.parameters), 1) # 'pretty` parameter was passed
|
|
335
340
|
out_mmif = Mmif(self.app.annotate(out_mmif))
|
|
336
341
|
self.assertEqual(len(out_mmif.views), 4)
|
|
337
|
-
views = list(out_mmif.views)
|
|
338
|
-
# insertion order is kept
|
|
339
|
-
self.assertEqual(views[0].metadata.timestamp, views[1].metadata.timestamp)
|
|
340
|
-
self.assertEqual(views[2].metadata.timestamp, views[3].metadata.timestamp)
|
|
341
|
-
self.assertTrue(views[1].metadata.timestamp < views[2].metadata.timestamp)
|
|
342
342
|
|
|
343
343
|
def test_run_id(self):
|
|
344
344
|
# first run
|
|
@@ -360,6 +360,31 @@ class TestClamsApp(unittest.TestCase):
|
|
|
360
360
|
else:
|
|
361
361
|
self.assertEqual(second_timestamp, view.metadata.timestamp)
|
|
362
362
|
|
|
363
|
+
def test_run_id_reuses_mmif_timestamp(self):
|
|
364
|
+
# the run ID is the timestamp mmif-python assigned to the first view
|
|
365
|
+
# of the run; clams-python does not generate its own
|
|
366
|
+
sentinel = datetime(2001, 2, 3, 4, 5, 6, tzinfo=timezone.utc)
|
|
367
|
+
original_annotate = self.app._annotate
|
|
368
|
+
|
|
369
|
+
def _annotate(mmif, **kwargs):
|
|
370
|
+
out = original_annotate(mmif, **kwargs)
|
|
371
|
+
next(reversed(out.views)).metadata.timestamp = sentinel
|
|
372
|
+
return out
|
|
373
|
+
|
|
374
|
+
# serialize the sentinel the same way mmif-python does
|
|
375
|
+
ref = ExampleInputMMIF.get_rawmmif()
|
|
376
|
+
ref.new_view().metadata.timestamp = sentinel
|
|
377
|
+
expected = next(reversed(
|
|
378
|
+
Mmif(ref.serialize(), validate=False).views)).metadata.timestamp
|
|
379
|
+
with patch.object(self.app, '_annotate', new=_annotate):
|
|
380
|
+
out_mmif = Mmif(self.app.annotate(self.in_mmif))
|
|
381
|
+
app_views = [v for v in out_mmif.views
|
|
382
|
+
if v.metadata.app == str(self.app.metadata.identifier)]
|
|
383
|
+
# one contentful view and one warning view
|
|
384
|
+
self.assertEqual(len(app_views), 2)
|
|
385
|
+
for view in app_views:
|
|
386
|
+
self.assertEqual(expected, view.metadata.timestamp)
|
|
387
|
+
|
|
363
388
|
def test_annotate_returns_invalid_mmif(self):
|
|
364
389
|
m = Mmif(self.in_mmif)
|
|
365
390
|
v = m.new_view()
|
|
@@ -430,21 +455,38 @@ class TestClamsApp(unittest.TestCase):
|
|
|
430
455
|
|
|
431
456
|
def test_error_handling(self):
|
|
432
457
|
params = {'raise_error': ['true'], 'pretty': ['true']}
|
|
433
|
-
in_mmif = Mmif(self.in_mmif)
|
|
434
458
|
try:
|
|
435
|
-
out_mmif = self.app.annotate(in_mmif, **params)
|
|
459
|
+
out_mmif = self.app.annotate(self.in_mmif, **params)
|
|
436
460
|
except Exception as e:
|
|
437
|
-
|
|
438
|
-
out_mmif_from_mmif = self.app.set_error_view(in_mmif, **params)
|
|
439
|
-
self.assertEqual(
|
|
440
|
-
out_mmif_from_mmif.views.get_last_contentful_view(),
|
|
441
|
-
out_mmif_from_str.views.get_last_contentful_view())
|
|
442
|
-
out_mmif = out_mmif_from_str
|
|
461
|
+
out_mmif = self.app.set_error_view(self.in_mmif, **params)
|
|
443
462
|
self.assertIsNotNone(out_mmif)
|
|
463
|
+
self.assertEqual(len(out_mmif.views), 1)
|
|
444
464
|
last_view: View = next(reversed(out_mmif.views))
|
|
445
465
|
self.assertEqual(len(last_view.metadata.contains), 0)
|
|
446
466
|
self.assertEqual(len(last_view.metadata.error), 2)
|
|
447
467
|
|
|
468
|
+
def test_error_view_keeps_previous_run(self):
|
|
469
|
+
# an error in a later run of the same app must not overwrite
|
|
470
|
+
# the views from an earlier, successful run
|
|
471
|
+
first = Mmif(self.app.annotate(self.in_mmif))
|
|
472
|
+
params = {'raise_error': ['true']}
|
|
473
|
+
try:
|
|
474
|
+
self.app.annotate(first.serialize(), **params)
|
|
475
|
+
except Exception:
|
|
476
|
+
out_mmif = self.app.set_error_view(first.serialize(), **params)
|
|
477
|
+
self.assertEqual(len(out_mmif.views), len(first.views) + 1)
|
|
478
|
+
for before, after in zip(first.views, out_mmif.views):
|
|
479
|
+
self.assertEqual(before.id, after.id)
|
|
480
|
+
self.assertFalse(after.has_error())
|
|
481
|
+
self.assertEqual(len(before.annotations), len(after.annotations))
|
|
482
|
+
self.assertEqual(before.metadata.timestamp,
|
|
483
|
+
after.metadata.timestamp)
|
|
484
|
+
error_view: View = next(reversed(out_mmif.views))
|
|
485
|
+
self.assertTrue(error_view.has_error())
|
|
486
|
+
self.assertEqual(len(error_view.annotations), 0)
|
|
487
|
+
self.assertNotIn(error_view.metadata.timestamp,
|
|
488
|
+
[v.metadata.timestamp for v in first.views])
|
|
489
|
+
|
|
448
490
|
def test_empty_view_becomes_warning_view(self):
|
|
449
491
|
class EmptyClamsApp(clams.app.ClamsApp):
|
|
450
492
|
def _appmetadata(self):
|
|
@@ -479,6 +521,65 @@ class TestClamsApp(unittest.TestCase):
|
|
|
479
521
|
self.assertEqual(len(view.annotations), 1)
|
|
480
522
|
self.assertFalse(view.has_warnings())
|
|
481
523
|
|
|
524
|
+
def _in_mmif_with_earlier_view(self):
|
|
525
|
+
in_mmif = Mmif(self.in_mmif)
|
|
526
|
+
doc_id = next(iter(in_mmif.documents)).id
|
|
527
|
+
earlier = in_mmif.new_view()
|
|
528
|
+
earlier.metadata.app = 'http://apps.clams.ai/earlier-app/v1'
|
|
529
|
+
earlier.new_contain(AnnotationTypes.TimeFrame, document=doc_id)
|
|
530
|
+
earlier.new_annotation(AnnotationTypes.TimeFrame, start=0, end=1)
|
|
531
|
+
return in_mmif.serialize()
|
|
532
|
+
|
|
533
|
+
def test_document_properties_go_to_a_new_signed_view(self):
|
|
534
|
+
class DocPropClamsApp(clams.app.ClamsApp):
|
|
535
|
+
def _appmetadata(self):
|
|
536
|
+
pass
|
|
537
|
+
def _annotate(self, mmif, **kwargs):
|
|
538
|
+
next(iter(mmif.documents)).add_property('duration', 1000)
|
|
539
|
+
return mmif
|
|
540
|
+
app = DocPropClamsApp()
|
|
541
|
+
in_str = self._in_mmif_with_earlier_view()
|
|
542
|
+
out_str = app.annotate(in_str)
|
|
543
|
+
self.assertEqual(json.loads(in_str)['views'][0],
|
|
544
|
+
json.loads(out_str)['views'][0])
|
|
545
|
+
out_views = list(Mmif(out_str).views)
|
|
546
|
+
self.assertEqual(len(out_views), 2)
|
|
547
|
+
self.assertEqual(out_views[1].metadata.app,
|
|
548
|
+
str(app.metadata.identifier))
|
|
549
|
+
records = list(out_views[1].get_annotations(AnnotationTypes.Annotation))
|
|
550
|
+
self.assertEqual(len(records), 1)
|
|
551
|
+
self.assertEqual(records[0].get_property('duration'), 1000)
|
|
552
|
+
self.assertFalse(out_views[1].has_warnings())
|
|
553
|
+
|
|
554
|
+
def test_earlier_views_are_written_unchanged(self):
|
|
555
|
+
in_str = self._in_mmif_with_earlier_view()
|
|
556
|
+
out_str = self.app.annotate(in_str)
|
|
557
|
+
self.assertEqual(json.loads(in_str)['views'][0],
|
|
558
|
+
json.loads(out_str)['views'][0])
|
|
559
|
+
|
|
560
|
+
def test_failed_run_leaves_mmif_reusable(self):
|
|
561
|
+
class FlakyClamsApp(clams.app.ClamsApp):
|
|
562
|
+
attempts = 0
|
|
563
|
+
def _appmetadata(self):
|
|
564
|
+
pass
|
|
565
|
+
def _annotate(self, mmif, **kwargs):
|
|
566
|
+
self.attempts += 1
|
|
567
|
+
if self.attempts == 1:
|
|
568
|
+
raise ValueError('first attempt fails')
|
|
569
|
+
new_view = mmif.new_view()
|
|
570
|
+
self.sign_view(new_view, kwargs)
|
|
571
|
+
new_view.new_contain(AnnotationTypes.TimeFrame)
|
|
572
|
+
new_view.new_annotation(AnnotationTypes.TimeFrame, start=0, end=1)
|
|
573
|
+
return mmif
|
|
574
|
+
app = FlakyClamsApp()
|
|
575
|
+
in_mmif = Mmif(self.in_mmif)
|
|
576
|
+
with self.assertRaises(ValueError):
|
|
577
|
+
app.annotate(in_mmif)
|
|
578
|
+
self.assertEqual(len(Mmif(app.annotate(in_mmif)).views), 1)
|
|
579
|
+
|
|
580
|
+
def test_empty_output_warning_is_importable_from_clams(self):
|
|
581
|
+
self.assertIs(clams.app.EmptyOutputWarning, EmptyOutputWarning)
|
|
582
|
+
|
|
482
583
|
def test_self_reported_error_view_is_not_pruned(self):
|
|
483
584
|
class ErroringClamsApp(clams.app.ClamsApp):
|
|
484
585
|
def _appmetadata(self):
|
|
@@ -494,6 +595,53 @@ class TestClamsApp(unittest.TestCase):
|
|
|
494
595
|
self.assertTrue(view.has_error())
|
|
495
596
|
self.assertFalse(view.has_warnings())
|
|
496
597
|
|
|
598
|
+
def test_self_reported_warning_view_is_not_pruned(self):
|
|
599
|
+
class WarningClamsApp(clams.app.ClamsApp):
|
|
600
|
+
def _appmetadata(self):
|
|
601
|
+
pass
|
|
602
|
+
def _annotate(self, mmif, **kwargs):
|
|
603
|
+
new_view = mmif.new_view()
|
|
604
|
+
self.sign_view(new_view, kwargs)
|
|
605
|
+
new_view.metadata.warnings = ['simulated warning']
|
|
606
|
+
return mmif
|
|
607
|
+
out_mmif = Mmif(WarningClamsApp().annotate(self.in_mmif))
|
|
608
|
+
self.assertEqual(len(out_mmif.views), 1)
|
|
609
|
+
view = next(iter(out_mmif.views))
|
|
610
|
+
self.assertEqual(view.metadata.warnings, ['simulated warning'])
|
|
611
|
+
|
|
612
|
+
def test_document_properties_stay_in_own_view(self):
|
|
613
|
+
# an app whose only output is document properties keeps its own view;
|
|
614
|
+
# the generated `Annotation`s must not go to an earlier app's view
|
|
615
|
+
class DocPropsClamsApp(clams.app.ClamsApp):
|
|
616
|
+
def _appmetadata(self):
|
|
617
|
+
pass
|
|
618
|
+
def _annotate(self, mmif, **kwargs):
|
|
619
|
+
new_view = mmif.new_view()
|
|
620
|
+
self.sign_view(new_view, kwargs)
|
|
621
|
+
mmif['v1'].add_property('duration', 1234)
|
|
622
|
+
mmif['t1'].add_property('author', 'me')
|
|
623
|
+
return mmif
|
|
624
|
+
in_mmif = Mmif(self.in_mmif)
|
|
625
|
+
earlier = in_mmif.new_view()
|
|
626
|
+
earlier.metadata.app = 'http://apps.clams.ai/earlier-app/v1'
|
|
627
|
+
earlier.new_annotation(AnnotationTypes.TimeFrame, start=0, end=10)
|
|
628
|
+
app = DocPropsClamsApp()
|
|
629
|
+
out_mmif = Mmif(app.annotate(in_mmif.serialize()))
|
|
630
|
+
self.assertEqual(
|
|
631
|
+
0, len(list(out_mmif[earlier.id].get_annotations(AnnotationTypes.Annotation))))
|
|
632
|
+
own_views = [v for v in out_mmif.views
|
|
633
|
+
if v.metadata.app == str(app.metadata.identifier)]
|
|
634
|
+
self.assertEqual(len(own_views), 1)
|
|
635
|
+
self.assertFalse(own_views[0].has_warnings())
|
|
636
|
+
# one `Annotation` per document, even though `serialize()` converts
|
|
637
|
+
# the same pending properties a second time
|
|
638
|
+
anns = list(own_views[0].get_annotations(AnnotationTypes.Annotation))
|
|
639
|
+
self.assertEqual(len(anns), 2)
|
|
640
|
+
props = {a.get_property('document'):
|
|
641
|
+
{k: v for k, v in a.properties.items() if k not in ('id', 'document')}
|
|
642
|
+
for a in anns}
|
|
643
|
+
self.assertEqual({'v1': {'duration': 1234}, 't1': {'author': 'me'}}, props)
|
|
644
|
+
|
|
497
645
|
def test_gpu_mem_fields_default_zero(self):
|
|
498
646
|
"""GPU memory fields default to 0."""
|
|
499
647
|
metadata = AppMetadata(
|
|
@@ -7,6 +7,7 @@ import io
|
|
|
7
7
|
import sys
|
|
8
8
|
import tempfile
|
|
9
9
|
import unittest
|
|
10
|
+
import warnings
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from unittest import mock
|
|
12
13
|
|
|
@@ -16,12 +17,25 @@ from clams.app import PromptTask
|
|
|
16
17
|
from clams.develop import CookieCutter
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
class TestRecipeDefaults(unittest.TestCase):
|
|
21
|
+
|
|
22
|
+
def test_default_recipes_leave_out_deprecated(self):
|
|
23
|
+
with warnings.catch_warnings():
|
|
24
|
+
warnings.simplefilter('error')
|
|
25
|
+
cutter = CookieCutter(
|
|
26
|
+
name='app', outdir=tempfile.gettempdir(), recipes=[])
|
|
27
|
+
self.assertTrue(cutter.recipes)
|
|
28
|
+
self.assertNotIn('utl-tf', cutter.recipes)
|
|
29
|
+
|
|
30
|
+
|
|
19
31
|
class TestUtlTfRecipe(unittest.TestCase):
|
|
20
32
|
|
|
21
33
|
def setUp(self):
|
|
22
34
|
tmp = tempfile.TemporaryDirectory()
|
|
23
35
|
self.addCleanup(tmp.cleanup)
|
|
24
|
-
|
|
36
|
+
# `utl-tf` is deprecated, and asking for it warns
|
|
37
|
+
with self.assertWarns(FutureWarning), \
|
|
38
|
+
contextlib.redirect_stdout(io.StringIO()):
|
|
25
39
|
CookieCutter(
|
|
26
40
|
name='tfapp', outdir=tmp.name, recipes=['utl-tf']).bake()
|
|
27
41
|
path = Path(tmp.name) / 'tfapp' / 'utils' / 'timeframe.py'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/.dockerignore.template
RENAMED
|
File without changes
|
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/Containerfile.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/app/requirements.txt.template
RENAMED
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/gha/for-clams-team.md.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/publish.yml.template
RENAMED
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.2}/clams/develop/templates/utl-tf/__init__.py.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|