clams-python 1.8.1__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.
Files changed (103) hide show
  1. {clams_python-1.8.1 → clams_python-1.8.2}/CHANGELOG.md +14 -0
  2. {clams_python-1.8.1/clams_python.egg-info → clams_python-1.8.2}/PKG-INFO +2 -2
  3. {clams_python-1.8.1 → clams_python-1.8.2}/clams/app/__init__.py +34 -64
  4. {clams_python-1.8.1 → clams_python-1.8.2/clams_python.egg-info}/PKG-INFO +2 -2
  5. {clams_python-1.8.1 → clams_python-1.8.2}/clams_python.egg-info/requires.txt +1 -1
  6. clams_python-1.8.2/clams_python.egg-info/scm_version.json +8 -0
  7. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/target-versions.csv +1 -0
  8. {clams_python-1.8.1 → clams_python-1.8.2}/pyproject.toml +1 -1
  9. {clams_python-1.8.1 → clams_python-1.8.2}/tests/test_clamsapp.py +63 -1
  10. clams_python-1.8.1/clams_python.egg-info/scm_version.json +0 -8
  11. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/codecov.yml +0 -0
  12. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/container.yml +0 -0
  13. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/containers.yml +0 -0
  14. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/issue-assign.yml +0 -0
  15. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/issue-close.yml +0 -0
  16. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/issue-infra-project.yml +0 -0
  17. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/publish.yml +0 -0
  18. {clams_python-1.8.1 → clams_python-1.8.2}/.github/workflows/release-check.yml +0 -0
  19. {clams_python-1.8.1 → clams_python-1.8.2}/.gitignore +0 -0
  20. {clams_python-1.8.1 → clams_python-1.8.2}/CONTRIBUTING.md +0 -0
  21. {clams_python-1.8.1 → clams_python-1.8.2}/LICENSE +0 -0
  22. {clams_python-1.8.1 → clams_python-1.8.2}/README.md +0 -0
  23. {clams_python-1.8.1 → clams_python-1.8.2}/build-tools/build.py +0 -0
  24. {clams_python-1.8.1 → clams_python-1.8.2}/build-tools/clean.py +0 -0
  25. {clams_python-1.8.1 → clams_python-1.8.2}/build-tools/docs.py +0 -0
  26. {clams_python-1.8.1 → clams_python-1.8.2}/build-tools/prep_release.py +0 -0
  27. {clams_python-1.8.1 → clams_python-1.8.2}/build-tools/publish.py +0 -0
  28. {clams_python-1.8.1 → clams_python-1.8.2}/build-tools/test.py +0 -0
  29. {clams_python-1.8.1 → clams_python-1.8.2}/clams/__init__.py +0 -0
  30. {clams_python-1.8.1 → clams_python-1.8.2}/clams/appmetadata/__init__.py +0 -0
  31. {clams_python-1.8.1 → clams_python-1.8.2}/clams/backends/__init__.py +0 -0
  32. {clams_python-1.8.1 → clams_python-1.8.2}/clams/backends/hf.py +0 -0
  33. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/__init__.py +0 -0
  34. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/.dockerignore.template +0 -0
  35. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/.gitignore.template +0 -0
  36. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/Containerfile.template +0 -0
  37. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/LICENSE.template +0 -0
  38. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/README.md.template +0 -0
  39. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/app.py.template +0 -0
  40. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/cli.py.template +0 -0
  41. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/metadata.py.template +0 -0
  42. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/app/requirements.txt.template +0 -0
  43. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
  44. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
  45. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
  46. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
  47. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
  48. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/utl-tf/__init__.py.template +0 -0
  49. {clams_python-1.8.1 → clams_python-1.8.2}/clams/develop/templates/utl-tf/timeframe.py.template +0 -0
  50. {clams_python-1.8.1 → clams_python-1.8.2}/clams/envelop/__init__.py +0 -0
  51. {clams_python-1.8.1 → clams_python-1.8.2}/clams/restify/__init__.py +0 -0
  52. {clams_python-1.8.1 → clams_python-1.8.2}/clams/serve/__init__.py +0 -0
  53. {clams_python-1.8.1 → clams_python-1.8.2}/clams/ver/__init__.py +0 -0
  54. {clams_python-1.8.1 → clams_python-1.8.2}/clams_python.egg-info/SOURCES.txt +0 -0
  55. {clams_python-1.8.1 → clams_python-1.8.2}/clams_python.egg-info/dependency_links.txt +0 -0
  56. {clams_python-1.8.1 → clams_python-1.8.2}/clams_python.egg-info/entry_points.txt +0 -0
  57. {clams_python-1.8.1 → clams_python-1.8.2}/clams_python.egg-info/scm_file_list.json +0 -0
  58. {clams_python-1.8.1 → clams_python-1.8.2}/clams_python.egg-info/top_level.txt +0 -0
  59. {clams_python-1.8.1 → clams_python-1.8.2}/container/Containerfile +0 -0
  60. {clams_python-1.8.1 → clams_python-1.8.2}/container/ffmpeg-hf.containerfile +0 -0
  61. {clams_python-1.8.1 → clams_python-1.8.2}/container/ffmpeg-hf4.containerfile +0 -0
  62. {clams_python-1.8.1 → clams_python-1.8.2}/container/ffmpeg-hf5.containerfile +0 -0
  63. {clams_python-1.8.1 → clams_python-1.8.2}/container/ffmpeg-tf2.containerfile +0 -0
  64. {clams_python-1.8.1 → clams_python-1.8.2}/container/ffmpeg-torch2.containerfile +0 -0
  65. {clams_python-1.8.1 → clams_python-1.8.2}/container/ffmpeg.containerfile +0 -0
  66. {clams_python-1.8.1 → clams_python-1.8.2}/container/generate_containers_yml.py +0 -0
  67. {clams_python-1.8.1 → clams_python-1.8.2}/container/hf.containerfile +0 -0
  68. {clams_python-1.8.1 → clams_python-1.8.2}/container/hf4.containerfile +0 -0
  69. {clams_python-1.8.1 → clams_python-1.8.2}/container/hf5.containerfile +0 -0
  70. {clams_python-1.8.1 → clams_python-1.8.2}/container/jdk8.containerfile +0 -0
  71. {clams_python-1.8.1 → clams_python-1.8.2}/container/opencv4-hf.containerfile +0 -0
  72. {clams_python-1.8.1 → clams_python-1.8.2}/container/opencv4-hf4.containerfile +0 -0
  73. {clams_python-1.8.1 → clams_python-1.8.2}/container/opencv4-hf5.containerfile +0 -0
  74. {clams_python-1.8.1 → clams_python-1.8.2}/container/opencv4-tf2.containerfile +0 -0
  75. {clams_python-1.8.1 → clams_python-1.8.2}/container/opencv4-torch2.containerfile +0 -0
  76. {clams_python-1.8.1 → clams_python-1.8.2}/container/opencv4.containerfile +0 -0
  77. {clams_python-1.8.1 → clams_python-1.8.2}/container/tf2.containerfile +0 -0
  78. {clams_python-1.8.1 → clams_python-1.8.2}/container/torch2.containerfile +0 -0
  79. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/app-baseclasses.rst +0 -0
  80. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/appdirectory.rst +0 -0
  81. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/appmetadata.rst +0 -0
  82. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/autodoc/clams.app.rst +0 -0
  83. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/autodoc/clams.appmetadata.rst +0 -0
  84. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/autodoc/clams.backends.rst +0 -0
  85. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/autodoc/clams.mmif_utils.rst +0 -0
  86. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/autodoc/clams.restify.rst +0 -0
  87. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/clamsapp.rst +0 -0
  88. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/cli.rst +0 -0
  89. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/conf.py +0 -0
  90. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/gpu-apps.rst +0 -0
  91. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/index.rst +0 -0
  92. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/input-output.rst +0 -0
  93. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/introduction.rst +0 -0
  94. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/modules.rst +0 -0
  95. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/runtime-params.rst +0 -0
  96. {clams_python-1.8.1 → clams_python-1.8.2}/documentation/tutorial.md +0 -0
  97. {clams_python-1.8.1 → clams_python-1.8.2}/setup.cfg +0 -0
  98. {clams_python-1.8.1 → clams_python-1.8.2}/tests/metadata.py +0 -0
  99. {clams_python-1.8.1 → clams_python-1.8.2}/tests/pillow-logo.png +0 -0
  100. {clams_python-1.8.1 → clams_python-1.8.2}/tests/test_backends_hf.py +0 -0
  101. {clams_python-1.8.1 → clams_python-1.8.2}/tests/test_develop.py +0 -0
  102. {clams_python-1.8.1 → clams_python-1.8.2}/tests/test_envelope.py +0 -0
  103. {clams_python-1.8.1 → clams_python-1.8.2}/tests/test_promptable.py +0 -0
@@ -1,4 +1,18 @@
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
+
2
16
  ## releasing 1.8.0 (2026-10-06)
3
17
  ### Overview
4
18
  Adds runtime parameters that cap the number of images per prompt in promptable apps, and updates `mmif-python` to 1.5.4.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clams-python
3
- Version: 1.8.1
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.6.0
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 Mmif, Document, DocumentTypes, View, AnnotationTypes
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,70 +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
- with warnings.catch_warnings(record=True) as ws:
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()
247
- new_views = [v for v in annotated.views if v.id not in existing_view_ids]
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())
250
- for v in new_views:
251
- if is_empty(v):
252
- del annotated.views._items[v.id] # no public API to remove a view (mmif-python)
253
- if all(is_empty(v) for v in new_views):
254
- warnings.warn(f'App "{self.metadata.identifier}" produced no annotations for this '
255
- f'input; the input may lack expected content, or there may genuinely '
256
- f'be nothing to annotate.', EmptyOutputWarning)
257
- if ws:
258
- issued_warnings.extend(ws)
259
- if issued_warnings:
260
- warnings_view = annotated.new_view()
261
- self.sign_view(warnings_view, refined)
262
- warnings_view.metadata.warnings = issued_warnings
263
- td = datetime.now() - t
264
- runningTime = refined.get('runningTime', False)
265
- hwFetch = refined.get('hwFetch', False)
266
- runtime_recs: Dict[str, Any] = {}
267
- if hwFetch:
268
- import multiprocessing
269
- import platform, shutil, subprocess
270
- runtime_recs['cpu'] = f"{platform.machine()}, {multiprocessing.cpu_count()} cores"
271
- runtime_recs['cuda'] = []
272
- # Use cuda_profiler data if available, otherwise fallback to nvidia-smi
273
- if cuda_profiler:
274
- for gpu_name, mem_info in cuda_profiler.items():
275
- total_str = self._cuda_memory_to_str(mem_info['total'])
276
- available_str = self._cuda_memory_to_str(mem_info['available_before'])
277
- peak_str = self._cuda_memory_to_str(mem_info['peak'])
278
- runtime_recs['cuda'].append(
279
- f"{gpu_name}, {total_str} total, {available_str} available, {peak_str} peak used"
280
- )
281
- elif shutil.which('nvidia-smi'):
282
- for gpu in subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total', '--format=csv,noheader'],
283
- stdout=subprocess.PIPE).stdout.decode('utf-8').strip().split('\n'):
284
- name, mem = gpu.split(', ')
285
- runtime_recs['cuda'].append(self._cuda_device_name_concat(name, mem))
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:
295
- annotated_view.metadata.timestamp = run_id
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))
296
267
  profiling_data: Dict[str, Any] = {}
297
268
  if runningTime:
298
269
  profiling_data['runningTime'] = str(td)
299
270
  if len(runtime_recs) > 0:
300
271
  profiling_data['hardware'] = runtime_recs
301
272
  if profiling_data:
302
- annotated_view.metadata.set_additional_property('appProfiling', profiling_data)
303
-
304
- return annotated.serialize(pretty=pretty, sanitize=True)
273
+ producer.set_view_metadata('appProfiling', profiling_data)
274
+ return annotated.serialize(pretty=pretty, sanitize=True)
305
275
 
306
276
  @abstractmethod
307
277
  def _annotate(self, mmif: Mmif, _raw_parameters=None, **refined_parameters) -> Mmif:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clams-python
3
- Version: 1.8.1
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.6.0
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,4 @@
1
- mmif-python==1.6.0
1
+ mmif-python==1.7.0
2
2
  Flask>=2
3
3
  Flask-RESTful>=0.3.9
4
4
  gunicorn>=20
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.8.2",
3
+ "distance": 0,
4
+ "node": "gde113a8698841b5578249f509461bf7e4b00d521",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-11-01"
8
+ }
@@ -1,4 +1,5 @@
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/>`__
2
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/>`__
3
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/>`__
4
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/>`__
@@ -19,7 +19,7 @@ classifiers = [
19
19
  "Programming Language :: Python :: 3 :: Only",
20
20
  ]
21
21
  dependencies = [
22
- "mmif-python==1.6.0",
22
+ "mmif-python==1.7.0",
23
23
  "Flask>=2",
24
24
  "Flask-RESTful>=0.3.9",
25
25
  "gunicorn>=20",
@@ -11,7 +11,10 @@ from unittest.mock import patch
11
11
 
12
12
  import jsonschema
13
13
  import pytest
14
- from mmif import Mmif, Document, DocumentTypes, AnnotationTypes, View, __specver__
14
+ from mmif import (
15
+ Mmif, Document, DocumentTypes, AnnotationTypes, View, EmptyOutputWarning,
16
+ __specver__,
17
+ )
15
18
  from mmif.serialize.mmif import ViewsList
16
19
 
17
20
  import clams.app
@@ -518,6 +521,65 @@ class TestClamsApp(unittest.TestCase):
518
521
  self.assertEqual(len(view.annotations), 1)
519
522
  self.assertFalse(view.has_warnings())
520
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
+
521
583
  def test_self_reported_error_view_is_not_pruned(self):
522
584
  class ErroringClamsApp(clams.app.ClamsApp):
523
585
  def _appmetadata(self):
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.8.1",
3
- "distance": 0,
4
- "node": "g7c4f92f3cf112bc39ac00f98b570fa3a77dde470",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-27"
8
- }
File without changes
File without changes
File without changes
File without changes