clams-python 1.8.0__tar.gz → 1.8.1__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.1}/CHANGELOG.md +16 -0
- {clams_python-1.8.0/clams_python.egg-info → clams_python-1.8.1}/PKG-INFO +2 -2
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/app/__init__.py +33 -24
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/__init__.py +18 -3
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/utl-tf/timeframe.py.template +8 -0
- {clams_python-1.8.0 → clams_python-1.8.1/clams_python.egg-info}/PKG-INFO +2 -2
- {clams_python-1.8.0 → clams_python-1.8.1}/clams_python.egg-info/requires.txt +1 -1
- clams_python-1.8.1/clams_python.egg-info/scm_version.json +8 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/target-versions.csv +1 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/pyproject.toml +1 -1
- {clams_python-1.8.0 → clams_python-1.8.1}/tests/test_clamsapp.py +99 -13
- {clams_python-1.8.0 → clams_python-1.8.1}/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.1}/.github/workflows/codecov.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.github/workflows/container.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.github/workflows/containers.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.github/workflows/issue-assign.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.github/workflows/issue-close.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.github/workflows/issue-infra-project.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.github/workflows/publish.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.github/workflows/release-check.yml +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/.gitignore +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/CONTRIBUTING.md +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/LICENSE +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/README.md +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/build-tools/build.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/build-tools/clean.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/build-tools/docs.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/build-tools/prep_release.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/build-tools/publish.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/build-tools/test.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/appmetadata/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/backends/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/backends/hf.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/.dockerignore.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/.gitignore.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/Containerfile.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/LICENSE.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/README.md.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/app.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/cli.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/metadata.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/app/requirements.txt.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/develop/templates/utl-tf/__init__.py.template +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/envelop/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/restify/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/serve/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams/ver/__init__.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams_python.egg-info/SOURCES.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams_python.egg-info/dependency_links.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams_python.egg-info/entry_points.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams_python.egg-info/scm_file_list.json +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/clams_python.egg-info/top_level.txt +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/Containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/ffmpeg-hf.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/ffmpeg-hf4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/ffmpeg-hf5.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/ffmpeg-tf2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/ffmpeg-torch2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/ffmpeg.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/generate_containers_yml.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/hf.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/hf4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/hf5.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/jdk8.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/opencv4-hf.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/opencv4-hf4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/opencv4-hf5.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/opencv4-tf2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/opencv4-torch2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/opencv4.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/tf2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/container/torch2.containerfile +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/app-baseclasses.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/appdirectory.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/appmetadata.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/autodoc/clams.app.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/autodoc/clams.appmetadata.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/autodoc/clams.backends.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/autodoc/clams.mmif_utils.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/autodoc/clams.restify.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/clamsapp.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/cli.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/conf.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/gpu-apps.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/index.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/input-output.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/introduction.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/modules.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/runtime-params.rst +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/documentation/tutorial.md +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/setup.cfg +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/tests/metadata.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/tests/pillow-logo.png +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/tests/test_backends_hf.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/tests/test_envelope.py +0 -0
- {clams_python-1.8.0 → clams_python-1.8.1}/tests/test_promptable.py +0 -0
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
|
|
2
|
+
## releasing 1.8.0 (2026-10-06)
|
|
3
|
+
### Overview
|
|
4
|
+
Adds runtime parameters that cap the number of images per prompt in promptable apps, and updates `mmif-python` to 1.5.4.
|
|
5
|
+
|
|
6
|
+
### Additions
|
|
7
|
+
|
|
8
|
+
* `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).
|
|
9
|
+
|
|
10
|
+
### Changes
|
|
11
|
+
|
|
12
|
+
* `mmif-python` is updated to 1.5.4, which fixes image extraction from truncated or otherwise incomplete video files.
|
|
13
|
+
* (for contributors) Test-time type checking moved from `pytype` to `mypy` (https://github.com/clamsproject/clams-python/issues/285).
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
2
18
|
## releasing 1.7.4 (2026-09-01)
|
|
3
19
|
### Overview
|
|
4
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clams-python
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.1
|
|
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.6.0
|
|
17
17
|
Requires-Dist: Flask>=2
|
|
18
18
|
Requires-Dist: Flask-RESTful>=0.3.9
|
|
19
19
|
Requires-Dist: gunicorn>=20
|
|
@@ -240,8 +240,13 @@ class ClamsApp(ABC):
|
|
|
240
240
|
t = datetime.now()
|
|
241
241
|
with warnings.catch_warnings(record=True) as ws:
|
|
242
242
|
annotated, cuda_profiler = self._profile_cuda_memory(self._annotate)(mmif, **refined)
|
|
243
|
+
# turn pending document properties into `Annotation`s now, so a
|
|
244
|
+
# view whose only output is document properties is not empty;
|
|
245
|
+
# `serialize()` repeats this later, skipping values already written
|
|
246
|
+
annotated.generate_capital_annotations()
|
|
243
247
|
new_views = [v for v in annotated.views if v.id not in existing_view_ids]
|
|
244
|
-
|
|
248
|
+
# an app-set error or warnings view has no annotations, but is kept
|
|
249
|
+
is_empty = lambda v: not (v.has_content() or v.has_error() or v.has_warnings())
|
|
245
250
|
for v in new_views:
|
|
246
251
|
if is_empty(v):
|
|
247
252
|
del annotated.views._items[v.id] # no public API to remove a view (mmif-python)
|
|
@@ -255,8 +260,7 @@ class ClamsApp(ABC):
|
|
|
255
260
|
warnings_view = annotated.new_view()
|
|
256
261
|
self.sign_view(warnings_view, refined)
|
|
257
262
|
warnings_view.metadata.warnings = issued_warnings
|
|
258
|
-
|
|
259
|
-
td = run_id - t
|
|
263
|
+
td = datetime.now() - t
|
|
260
264
|
runningTime = refined.get('runningTime', False)
|
|
261
265
|
hwFetch = refined.get('hwFetch', False)
|
|
262
266
|
runtime_recs: Dict[str, Any] = {}
|
|
@@ -279,16 +283,23 @@ class ClamsApp(ABC):
|
|
|
279
283
|
stdout=subprocess.PIPE).stdout.decode('utf-8').strip().split('\n'):
|
|
280
284
|
name, mem = gpu.split(', ')
|
|
281
285
|
runtime_recs['cuda'].append(self._cuda_device_name_concat(name, mem))
|
|
282
|
-
for
|
|
283
|
-
|
|
286
|
+
new_app_views = [v for v in annotated.views
|
|
287
|
+
if v.id not in existing_view_ids
|
|
288
|
+
and v.metadata.app == str(self.metadata.identifier)]
|
|
289
|
+
# all views from this run share the timestamp that mmif-python
|
|
290
|
+
# assigned to the first of them, serving as the "run ID"
|
|
291
|
+
run_id = next((v.metadata.timestamp for v in new_app_views
|
|
292
|
+
if v.metadata.timestamp is not None), None)
|
|
293
|
+
for annotated_view in new_app_views:
|
|
294
|
+
if run_id is not None:
|
|
284
295
|
annotated_view.metadata.timestamp = run_id
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
296
|
+
profiling_data: Dict[str, Any] = {}
|
|
297
|
+
if runningTime:
|
|
298
|
+
profiling_data['runningTime'] = str(td)
|
|
299
|
+
if len(runtime_recs) > 0:
|
|
300
|
+
profiling_data['hardware'] = runtime_recs
|
|
301
|
+
if profiling_data:
|
|
302
|
+
annotated_view.metadata.set_additional_property('appProfiling', profiling_data)
|
|
292
303
|
|
|
293
304
|
return annotated.serialize(pretty=pretty, sanitize=True)
|
|
294
305
|
|
|
@@ -391,7 +402,9 @@ class ClamsApp(ABC):
|
|
|
391
402
|
"""
|
|
392
403
|
view.metadata.app = str(self.metadata.identifier)
|
|
393
404
|
if self.metadata.app_tags:
|
|
394
|
-
|
|
405
|
+
# item assignment stores the tags in the declared `appTags` field
|
|
406
|
+
# when mmif-python has one, and as an additional property otherwise
|
|
407
|
+
view.metadata['appTags'] = list(self.metadata.app_tags)
|
|
395
408
|
if self._RAW_PARAMS_KEY in runtime_conf:
|
|
396
409
|
for k, v in runtime_conf.items():
|
|
397
410
|
if k == self._RAW_PARAMS_KEY:
|
|
@@ -436,9 +449,11 @@ class ClamsApp(ABC):
|
|
|
436
449
|
|
|
437
450
|
def set_error_view(self, mmif: Union[str, dict, Mmif], **runtime_conf: List[str]) -> Mmif:
|
|
438
451
|
"""
|
|
439
|
-
A method to record an error instead of annotation results in
|
|
440
|
-
|
|
441
|
-
|
|
452
|
+
A method to record an error instead of annotation results in a new
|
|
453
|
+
view appended to the input MMIF. Existing views are left untouched,
|
|
454
|
+
including views from earlier runs of the same app. For logging
|
|
455
|
+
purpose, the runtime parameters used when the error occurred must be
|
|
456
|
+
passed as well.
|
|
442
457
|
|
|
443
458
|
:param mmif: input MMIF object
|
|
444
459
|
:param runtime_conf: parameters passed to annotate when the app encountered the error
|
|
@@ -448,14 +463,8 @@ class ClamsApp(ABC):
|
|
|
448
463
|
if isinstance(mmif, (bytes, str, dict)):
|
|
449
464
|
mmif, runtime_conf = unwrap_if_envelope(mmif, runtime_conf)
|
|
450
465
|
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)
|
|
466
|
+
error_view = mmif.new_view()
|
|
467
|
+
self.sign_view(error_view, runtime_conf)
|
|
459
468
|
exc_info = sys.exc_info()
|
|
460
469
|
error_view.set_error(f'{exc_info[0]}: {exc_info[1]}',
|
|
461
470
|
'\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.1}/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.1
|
|
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.6.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,5 @@
|
|
|
1
1
|
"``clams-python`` version","``mmif-python`` version","Target MMIF Specification"
|
|
2
|
+
`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
3
|
`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
4
|
`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
5
|
`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,7 +5,9 @@ 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
|
|
@@ -334,11 +336,6 @@ class TestClamsApp(unittest.TestCase):
|
|
|
334
336
|
self.assertEqual(len(v.metadata.parameters), 1) # 'pretty` parameter was passed
|
|
335
337
|
out_mmif = Mmif(self.app.annotate(out_mmif))
|
|
336
338
|
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
339
|
|
|
343
340
|
def test_run_id(self):
|
|
344
341
|
# first run
|
|
@@ -360,6 +357,31 @@ class TestClamsApp(unittest.TestCase):
|
|
|
360
357
|
else:
|
|
361
358
|
self.assertEqual(second_timestamp, view.metadata.timestamp)
|
|
362
359
|
|
|
360
|
+
def test_run_id_reuses_mmif_timestamp(self):
|
|
361
|
+
# the run ID is the timestamp mmif-python assigned to the first view
|
|
362
|
+
# of the run; clams-python does not generate its own
|
|
363
|
+
sentinel = datetime(2001, 2, 3, 4, 5, 6, tzinfo=timezone.utc)
|
|
364
|
+
original_annotate = self.app._annotate
|
|
365
|
+
|
|
366
|
+
def _annotate(mmif, **kwargs):
|
|
367
|
+
out = original_annotate(mmif, **kwargs)
|
|
368
|
+
next(reversed(out.views)).metadata.timestamp = sentinel
|
|
369
|
+
return out
|
|
370
|
+
|
|
371
|
+
# serialize the sentinel the same way mmif-python does
|
|
372
|
+
ref = ExampleInputMMIF.get_rawmmif()
|
|
373
|
+
ref.new_view().metadata.timestamp = sentinel
|
|
374
|
+
expected = next(reversed(
|
|
375
|
+
Mmif(ref.serialize(), validate=False).views)).metadata.timestamp
|
|
376
|
+
with patch.object(self.app, '_annotate', new=_annotate):
|
|
377
|
+
out_mmif = Mmif(self.app.annotate(self.in_mmif))
|
|
378
|
+
app_views = [v for v in out_mmif.views
|
|
379
|
+
if v.metadata.app == str(self.app.metadata.identifier)]
|
|
380
|
+
# one contentful view and one warning view
|
|
381
|
+
self.assertEqual(len(app_views), 2)
|
|
382
|
+
for view in app_views:
|
|
383
|
+
self.assertEqual(expected, view.metadata.timestamp)
|
|
384
|
+
|
|
363
385
|
def test_annotate_returns_invalid_mmif(self):
|
|
364
386
|
m = Mmif(self.in_mmif)
|
|
365
387
|
v = m.new_view()
|
|
@@ -430,21 +452,38 @@ class TestClamsApp(unittest.TestCase):
|
|
|
430
452
|
|
|
431
453
|
def test_error_handling(self):
|
|
432
454
|
params = {'raise_error': ['true'], 'pretty': ['true']}
|
|
433
|
-
in_mmif = Mmif(self.in_mmif)
|
|
434
455
|
try:
|
|
435
|
-
out_mmif = self.app.annotate(in_mmif, **params)
|
|
456
|
+
out_mmif = self.app.annotate(self.in_mmif, **params)
|
|
436
457
|
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
|
|
458
|
+
out_mmif = self.app.set_error_view(self.in_mmif, **params)
|
|
443
459
|
self.assertIsNotNone(out_mmif)
|
|
460
|
+
self.assertEqual(len(out_mmif.views), 1)
|
|
444
461
|
last_view: View = next(reversed(out_mmif.views))
|
|
445
462
|
self.assertEqual(len(last_view.metadata.contains), 0)
|
|
446
463
|
self.assertEqual(len(last_view.metadata.error), 2)
|
|
447
464
|
|
|
465
|
+
def test_error_view_keeps_previous_run(self):
|
|
466
|
+
# an error in a later run of the same app must not overwrite
|
|
467
|
+
# the views from an earlier, successful run
|
|
468
|
+
first = Mmif(self.app.annotate(self.in_mmif))
|
|
469
|
+
params = {'raise_error': ['true']}
|
|
470
|
+
try:
|
|
471
|
+
self.app.annotate(first.serialize(), **params)
|
|
472
|
+
except Exception:
|
|
473
|
+
out_mmif = self.app.set_error_view(first.serialize(), **params)
|
|
474
|
+
self.assertEqual(len(out_mmif.views), len(first.views) + 1)
|
|
475
|
+
for before, after in zip(first.views, out_mmif.views):
|
|
476
|
+
self.assertEqual(before.id, after.id)
|
|
477
|
+
self.assertFalse(after.has_error())
|
|
478
|
+
self.assertEqual(len(before.annotations), len(after.annotations))
|
|
479
|
+
self.assertEqual(before.metadata.timestamp,
|
|
480
|
+
after.metadata.timestamp)
|
|
481
|
+
error_view: View = next(reversed(out_mmif.views))
|
|
482
|
+
self.assertTrue(error_view.has_error())
|
|
483
|
+
self.assertEqual(len(error_view.annotations), 0)
|
|
484
|
+
self.assertNotIn(error_view.metadata.timestamp,
|
|
485
|
+
[v.metadata.timestamp for v in first.views])
|
|
486
|
+
|
|
448
487
|
def test_empty_view_becomes_warning_view(self):
|
|
449
488
|
class EmptyClamsApp(clams.app.ClamsApp):
|
|
450
489
|
def _appmetadata(self):
|
|
@@ -494,6 +533,53 @@ class TestClamsApp(unittest.TestCase):
|
|
|
494
533
|
self.assertTrue(view.has_error())
|
|
495
534
|
self.assertFalse(view.has_warnings())
|
|
496
535
|
|
|
536
|
+
def test_self_reported_warning_view_is_not_pruned(self):
|
|
537
|
+
class WarningClamsApp(clams.app.ClamsApp):
|
|
538
|
+
def _appmetadata(self):
|
|
539
|
+
pass
|
|
540
|
+
def _annotate(self, mmif, **kwargs):
|
|
541
|
+
new_view = mmif.new_view()
|
|
542
|
+
self.sign_view(new_view, kwargs)
|
|
543
|
+
new_view.metadata.warnings = ['simulated warning']
|
|
544
|
+
return mmif
|
|
545
|
+
out_mmif = Mmif(WarningClamsApp().annotate(self.in_mmif))
|
|
546
|
+
self.assertEqual(len(out_mmif.views), 1)
|
|
547
|
+
view = next(iter(out_mmif.views))
|
|
548
|
+
self.assertEqual(view.metadata.warnings, ['simulated warning'])
|
|
549
|
+
|
|
550
|
+
def test_document_properties_stay_in_own_view(self):
|
|
551
|
+
# an app whose only output is document properties keeps its own view;
|
|
552
|
+
# the generated `Annotation`s must not go to an earlier app's view
|
|
553
|
+
class DocPropsClamsApp(clams.app.ClamsApp):
|
|
554
|
+
def _appmetadata(self):
|
|
555
|
+
pass
|
|
556
|
+
def _annotate(self, mmif, **kwargs):
|
|
557
|
+
new_view = mmif.new_view()
|
|
558
|
+
self.sign_view(new_view, kwargs)
|
|
559
|
+
mmif['v1'].add_property('duration', 1234)
|
|
560
|
+
mmif['t1'].add_property('author', 'me')
|
|
561
|
+
return mmif
|
|
562
|
+
in_mmif = Mmif(self.in_mmif)
|
|
563
|
+
earlier = in_mmif.new_view()
|
|
564
|
+
earlier.metadata.app = 'http://apps.clams.ai/earlier-app/v1'
|
|
565
|
+
earlier.new_annotation(AnnotationTypes.TimeFrame, start=0, end=10)
|
|
566
|
+
app = DocPropsClamsApp()
|
|
567
|
+
out_mmif = Mmif(app.annotate(in_mmif.serialize()))
|
|
568
|
+
self.assertEqual(
|
|
569
|
+
0, len(list(out_mmif[earlier.id].get_annotations(AnnotationTypes.Annotation))))
|
|
570
|
+
own_views = [v for v in out_mmif.views
|
|
571
|
+
if v.metadata.app == str(app.metadata.identifier)]
|
|
572
|
+
self.assertEqual(len(own_views), 1)
|
|
573
|
+
self.assertFalse(own_views[0].has_warnings())
|
|
574
|
+
# one `Annotation` per document, even though `serialize()` converts
|
|
575
|
+
# the same pending properties a second time
|
|
576
|
+
anns = list(own_views[0].get_annotations(AnnotationTypes.Annotation))
|
|
577
|
+
self.assertEqual(len(anns), 2)
|
|
578
|
+
props = {a.get_property('document'):
|
|
579
|
+
{k: v for k, v in a.properties.items() if k not in ('id', 'document')}
|
|
580
|
+
for a in anns}
|
|
581
|
+
self.assertEqual({'v1': {'duration': 1234}, 't1': {'author': 'me'}}, props)
|
|
582
|
+
|
|
497
583
|
def test_gpu_mem_fields_default_zero(self):
|
|
498
584
|
"""GPU memory fields default to 0."""
|
|
499
585
|
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.1}/clams/develop/templates/app/.dockerignore.template
RENAMED
|
File without changes
|
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.1}/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.1}/clams/develop/templates/app/requirements.txt.template
RENAMED
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.1}/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.1}/clams/develop/templates/gha/workflows/publish.yml.template
RENAMED
|
File without changes
|
{clams_python-1.8.0 → clams_python-1.8.1}/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
|