mmif-python 1.5.1__tar.gz → 1.5.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 (101) hide show
  1. {mmif_python-1.5.1 → mmif_python-1.5.2}/CHANGELOG.md +5 -0
  2. {mmif_python-1.5.1 → mmif_python-1.5.2}/CONTRIBUTING.md +1 -1
  3. {mmif_python-1.5.1 → mmif_python-1.5.2}/PKG-INFO +1 -1
  4. {mmif_python-1.5.1 → mmif_python-1.5.2}/build-tools/docs.py +23 -8
  5. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/target-versions.csv +1 -0
  6. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/serialize/view.py +19 -0
  7. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif_python.egg-info/PKG-INFO +1 -1
  8. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif_python.egg-info/SOURCES.txt +2 -0
  9. mmif_python-1.5.2/mmif_python.egg-info/scm_file_list.json +95 -0
  10. mmif_python-1.5.2/mmif_python.egg-info/scm_version.json +8 -0
  11. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/test_serialize.py +51 -1
  12. {mmif_python-1.5.1 → mmif_python-1.5.2}/.github/workflows/codecov.yml +0 -0
  13. {mmif_python-1.5.1 → mmif_python-1.5.2}/.github/workflows/issue-assign.yml +0 -0
  14. {mmif_python-1.5.1 → mmif_python-1.5.2}/.github/workflows/issue-close.yml +0 -0
  15. {mmif_python-1.5.1 → mmif_python-1.5.2}/.github/workflows/issue-infra-project.yml +0 -0
  16. {mmif_python-1.5.1 → mmif_python-1.5.2}/.github/workflows/publish.yml +0 -0
  17. {mmif_python-1.5.1 → mmif_python-1.5.2}/.github/workflows/release-check.yml +0 -0
  18. {mmif_python-1.5.1 → mmif_python-1.5.2}/.gitignore +0 -0
  19. {mmif_python-1.5.1 → mmif_python-1.5.2}/LICENSE +0 -0
  20. {mmif_python-1.5.1 → mmif_python-1.5.2}/README.md +0 -0
  21. {mmif_python-1.5.1 → mmif_python-1.5.2}/build-tools/build.py +0 -0
  22. {mmif_python-1.5.1 → mmif_python-1.5.2}/build-tools/clean.py +0 -0
  23. {mmif_python-1.5.1 → mmif_python-1.5.2}/build-tools/prep_release.py +0 -0
  24. {mmif_python-1.5.1 → mmif_python-1.5.2}/build-tools/publish.py +0 -0
  25. {mmif_python-1.5.1 → mmif_python-1.5.2}/build-tools/test.py +0 -0
  26. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/_mmif_example_builder/__init__.py +0 -0
  27. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/_templates/versions.html +0 -0
  28. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/cli.rst +0 -0
  29. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/conf.py +0 -0
  30. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/consumer-tutorial.rst +0 -0
  31. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/index.rst +0 -0
  32. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/introduction.rst +0 -0
  33. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/plugins.rst +0 -0
  34. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/summarizer.rst +0 -0
  35. {mmif_python-1.5.1 → mmif_python-1.5.2}/documentation/target-versions.rst +0 -0
  36. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/__init__.py +0 -0
  37. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/res/__init__.py +0 -0
  38. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/res/mmif.json +0 -0
  39. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/serialize/__init__.py +0 -0
  40. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/serialize/annotation.py +0 -0
  41. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/serialize/mmif.py +0 -0
  42. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/serialize/model.py +0 -0
  43. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/__init__.py +0 -0
  44. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/cli/__init__.py +0 -0
  45. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/cli/describe.py +0 -0
  46. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/cli/rewind.py +0 -0
  47. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/cli/source.py +0 -0
  48. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/cli/summarize.py +0 -0
  49. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/sequence_helper.py +0 -0
  50. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/summarizer/__init__.py +0 -0
  51. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/summarizer/config.py +0 -0
  52. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/summarizer/graph.py +0 -0
  53. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/summarizer/nodes.py +0 -0
  54. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/summarizer/summary.py +0 -0
  55. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/summarizer/utils.py +0 -0
  56. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/text_document_helper.py +0 -0
  57. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/timeunit_helper.py +0 -0
  58. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/video_document_helper.py +0 -0
  59. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/utils/workflow_helper.py +0 -0
  60. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/ver/__init__.py +0 -0
  61. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/vocabulary/__init__.py +0 -0
  62. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/vocabulary/annotation_types.py +0 -0
  63. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/vocabulary/base_types.py +0 -0
  64. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif/vocabulary/document_types.py +0 -0
  65. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif_docloc_http/__init__.py +0 -0
  66. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif_python.egg-info/dependency_links.txt +0 -0
  67. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif_python.egg-info/entry_points.txt +0 -0
  68. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif_python.egg-info/requires.txt +0 -0
  69. {mmif_python-1.5.1 → mmif_python-1.5.2}/mmif_python.egg-info/top_level.txt +0 -0
  70. {mmif_python-1.5.1 → mmif_python-1.5.2}/pyproject.toml +0 -0
  71. {mmif_python-1.5.1 → mmif_python-1.5.2}/setup.cfg +0 -0
  72. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/black-2997fps.mp4 +0 -0
  73. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/black-2997fps.webm +0 -0
  74. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/1.0.5-old-shortid.json +0 -0
  75. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/README.md +0 -0
  76. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/bars-tones-slates/raw.json +0 -0
  77. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/east-tesseract-typing/raw.json +0 -0
  78. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/everything/raw.json +0 -0
  79. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/examples.json +0 -0
  80. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/README.md +0 -0
  81. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/image-box-ocr-timex.json +0 -0
  82. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/image-box-ocr.json +0 -0
  83. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/image-box.json +0 -0
  84. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/image.json +0 -0
  85. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/slate-detect.json +0 -0
  86. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/slate-reco.json +0 -0
  87. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/video-demux.json +0 -0
  88. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/video-transcript-demux-fa.json +0 -0
  89. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/video-transcript-demux-fa.short.json +0 -0
  90. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/video-transcript-demux.json +0 -0
  91. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/video-transcript-sentencesplit.json +0 -0
  92. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/video-transcript.json +0 -0
  93. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/others/video.json +0 -0
  94. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/segmenter-kaldi-ner/raw.json +0 -0
  95. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif-examples/swt-1.0.mmif +0 -0
  96. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/mmif_examples.py +0 -0
  97. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/test_specver.py +0 -0
  98. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/test_utils.py +0 -0
  99. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/test_utils_cli.py +0 -0
  100. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/test_vocab.py +0 -0
  101. {mmif_python-1.5.1 → mmif_python-1.5.2}/tests/testsrc-2997fps-ptsoffset.mp4 +0 -0
@@ -1,4 +1,9 @@
1
1
 
2
+ ## releasing 1.5.1 (2026-06-19)
3
+ ### Overview
4
+ This patch contains a bug fix in VDH that couldn't handle some mkv-contained video files (https://github.com/clamsproject/mmif-python/issues/390) , and some other minor documentation generation bugs.
5
+
6
+
2
7
  ## releasing 1.5.0 (2026-05-27)
3
8
  ### Overview
4
9
 
@@ -162,7 +162,7 @@ To build documentation for a specific historical version (e.g., `v1.0.0`):
162
162
  python3 build-tools/docs.py --build-ver v1.0.0
163
163
  ```
164
164
 
165
- This runs the build in a sandboxed temporary directory. The output will be in `docs-test/<version>`.
165
+ This runs the build in a sandboxed temporary directory. A single `--build-ver` places the built docs **flat** in the output directory (`docs-test/` by default). The batch `--build-all-since` mode instead nests each version under a `<version>/` subdirectory of the output directory, so multiple versions can coexist. The flat single-version layout is what the CI docs-publish workflow relies on — it invokes `docs.py --build-ver <ver> --output-dir _docs` and uploads `_docs/` as-is, without needing to know whether the project is versioned.
166
166
 
167
167
  > [!NOTE]
168
168
  > In CI, documentation is built and published automatically by the `publish.yml` workflow via the shared `sdk-docs.yml`. The CI calls `docs.py --build-ver <version> --output-dir _docs`. All CLAMS SDK repos use the same `docs.py` CLI interface (`--build-ver`, `--output-dir`).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmif-python
3
- Version: 1.5.1
3
+ Version: 1.5.2
4
4
  Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: homepage, https://mmif.clams.ai
@@ -188,7 +188,8 @@ def linkcode_resolve(domain, info):
188
188
  return docs_build_dir
189
189
 
190
190
 
191
- def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path = None):
191
+ def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path = None,
192
+ versioned_subdir: bool = True):
192
193
  """
193
194
  Builds docs for a specific version in a sandbox.
194
195
  Uses local repo copy instead of cloning for efficiency.
@@ -197,6 +198,12 @@ def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path
197
198
  version: Git tag/ref to build
198
199
  output_base_dir: Directory to output built docs
199
200
  repo_path: Path to local repo to copy from (defaults to cwd)
201
+ versioned_subdir: when True (batch/from-scratch rebuild), the built
202
+ docs are placed under ``output_base_dir/<version>/`` so multiple
203
+ versions can coexist. When False (single build, as used by CI),
204
+ the docs are placed flat into ``output_base_dir`` so the shared
205
+ docs-publish workflow can upload them as-is without knowing about
206
+ versioning.
200
207
  """
201
208
  print(f"--- Running in Version Build Mode for version: {version} ---")
202
209
 
@@ -226,13 +233,19 @@ def build_docs_for_version(version: str, output_base_dir: Path, repo_path: Path
226
233
  built_docs_path = build_versioned_docs(env, source_path, version)
227
234
 
228
235
  print(f"\n--- Step 4: Copying built artifacts to output directory ---")
229
- version_output_dir = output_base_dir / version
230
- if version_output_dir.exists():
231
- print(f"Removing existing directory: {version_output_dir}")
232
- shutil.rmtree(version_output_dir)
233
- shutil.copytree(built_docs_path, version_output_dir)
236
+ if versioned_subdir:
237
+ dest = output_base_dir / version
238
+ if dest.exists():
239
+ print(f"Removing existing directory: {dest}")
240
+ shutil.rmtree(dest)
241
+ shutil.copytree(built_docs_path, dest)
242
+ else:
243
+ # flat: place the built docs directly into output_base_dir
244
+ output_base_dir.mkdir(parents=True, exist_ok=True)
245
+ shutil.copytree(built_docs_path, output_base_dir, dirs_exist_ok=True)
246
+ dest = output_base_dir
234
247
 
235
- print(f"\nDocumentation for {version} built successfully in: {version_output_dir}")
248
+ print(f"\nDocumentation for {version} built successfully in: {dest}")
236
249
 
237
250
 
238
251
  def get_all_tags():
@@ -317,7 +330,9 @@ def main():
317
330
  for version, error in failed:
318
331
  print(f" {version}: {error}")
319
332
  elif args.build_ver:
320
- build_docs_for_version(args.build_ver, output_dir)
333
+ # single build (CI + local single-version): flat output so the
334
+ # shared docs-publish workflow uploads it version-agnostically
335
+ build_docs_for_version(args.build_ver, output_dir, versioned_subdir=False)
321
336
  else:
322
337
  build_docs_local(Path.cwd(), output_dir)
323
338
 
@@ -1,4 +1,5 @@
1
1
  "``mmif-python`` version","Target MMIF Specification"
2
+ `1.5.2 <https://pypi.org/project/mmif-python/1.5.2/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
2
3
  `1.5.1 <https://pypi.org/project/mmif-python/1.5.1/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
3
4
  `1.5.0 <https://pypi.org/project/mmif-python/1.5.0/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
4
5
  `1.4.0 <https://pypi.org/project/mmif-python/1.4.0/>`__,`1.1.1 <https://mmif.clams.ai/1.1.1/>`__
@@ -82,6 +82,25 @@ class View(MmifObject):
82
82
  self._required_attributes = ["id", "metadata", "annotations"]
83
83
  super().__init__(view_obj)
84
84
  self._fix_old_short_ids()
85
+ self._rebuild_id_counts()
86
+
87
+ def _rebuild_id_counts(self):
88
+ """
89
+ Re-seed the per-view auto-ID counters from the annotations loaded during
90
+ deserialization.
91
+
92
+ ``_id_counts`` is not serialized, so a view read back from JSON starts
93
+ with an empty counter. Without this, the next :meth:`new_annotation`
94
+ regenerates an ID (e.g. ``an_1``) that already exists in the
95
+ deserialized view and :meth:`AnnotationsList.append` raises ``KeyError``.
96
+ See issue #393.
97
+ """
98
+ for annotation in self.annotations:
99
+ local_id = annotation.id.split(self.id_delimiter)[-1]
100
+ prefix, _, num = local_id.rpartition('_')
101
+ if prefix and num.isdigit():
102
+ self._id_counts[prefix] = max(
103
+ self._id_counts.get(prefix, 0), int(num))
85
104
 
86
105
  def _fix_old_short_ids(self):
87
106
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmif-python
3
- Version: 1.5.1
3
+ Version: 1.5.2
4
4
  Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: homepage, https://mmif.clams.ai
@@ -63,6 +63,8 @@ mmif_python.egg-info/SOURCES.txt
63
63
  mmif_python.egg-info/dependency_links.txt
64
64
  mmif_python.egg-info/entry_points.txt
65
65
  mmif_python.egg-info/requires.txt
66
+ mmif_python.egg-info/scm_file_list.json
67
+ mmif_python.egg-info/scm_version.json
66
68
  mmif_python.egg-info/top_level.txt
67
69
  tests/black-2997fps.mp4
68
70
  tests/black-2997fps.webm
@@ -0,0 +1,95 @@
1
+ {
2
+ "files": [
3
+ "README.md",
4
+ "LICENSE",
5
+ "pyproject.toml",
6
+ "CHANGELOG.md",
7
+ "CONTRIBUTING.md",
8
+ ".gitignore",
9
+ "build-tools/publish.py",
10
+ "build-tools/prep_release.py",
11
+ "build-tools/build.py",
12
+ "build-tools/docs.py",
13
+ "build-tools/clean.py",
14
+ "build-tools/test.py",
15
+ "documentation/target-versions.csv",
16
+ "documentation/consumer-tutorial.rst",
17
+ "documentation/target-versions.rst",
18
+ "documentation/index.rst",
19
+ "documentation/introduction.rst",
20
+ "documentation/cli.rst",
21
+ "documentation/plugins.rst",
22
+ "documentation/summarizer.rst",
23
+ "documentation/conf.py",
24
+ "documentation/_mmif_example_builder/__init__.py",
25
+ "documentation/_templates/versions.html",
26
+ "mmif/__init__.py",
27
+ "mmif/vocabulary/__init__.py",
28
+ "mmif/vocabulary/base_types.py",
29
+ "mmif/vocabulary/document_types.py",
30
+ "mmif/vocabulary/annotation_types.py",
31
+ "mmif/utils/__init__.py",
32
+ "mmif/utils/text_document_helper.py",
33
+ "mmif/utils/timeunit_helper.py",
34
+ "mmif/utils/workflow_helper.py",
35
+ "mmif/utils/video_document_helper.py",
36
+ "mmif/utils/sequence_helper.py",
37
+ "mmif/utils/summarizer/__init__.py",
38
+ "mmif/utils/summarizer/config.py",
39
+ "mmif/utils/summarizer/utils.py",
40
+ "mmif/utils/summarizer/summary.py",
41
+ "mmif/utils/summarizer/graph.py",
42
+ "mmif/utils/summarizer/nodes.py",
43
+ "mmif/utils/cli/__init__.py",
44
+ "mmif/utils/cli/source.py",
45
+ "mmif/utils/cli/rewind.py",
46
+ "mmif/utils/cli/summarize.py",
47
+ "mmif/utils/cli/describe.py",
48
+ "mmif/serialize/model.py",
49
+ "mmif/serialize/__init__.py",
50
+ "mmif/serialize/view.py",
51
+ "mmif/serialize/annotation.py",
52
+ "mmif/serialize/mmif.py",
53
+ "mmif/res/__init__.py",
54
+ "mmif/res/mmif.json",
55
+ "mmif/ver/__init__.py",
56
+ "tests/mmif_examples.py",
57
+ "tests/test_specver.py",
58
+ "tests/test_utils.py",
59
+ "tests/test_utils_cli.py",
60
+ "tests/test_vocab.py",
61
+ "tests/testsrc-2997fps-ptsoffset.mp4",
62
+ "tests/test_serialize.py",
63
+ "tests/black-2997fps.mp4",
64
+ "tests/black-2997fps.webm",
65
+ "tests/mmif-examples/README.md",
66
+ "tests/mmif-examples/examples.json",
67
+ "tests/mmif-examples/1.0.5-old-shortid.json",
68
+ "tests/mmif-examples/swt-1.0.mmif",
69
+ "tests/mmif-examples/segmenter-kaldi-ner/raw.json",
70
+ "tests/mmif-examples/everything/raw.json",
71
+ "tests/mmif-examples/others/video-transcript.json",
72
+ "tests/mmif-examples/others/image-box-ocr-timex.json",
73
+ "tests/mmif-examples/others/README.md",
74
+ "tests/mmif-examples/others/video.json",
75
+ "tests/mmif-examples/others/video-transcript-demux-fa.json",
76
+ "tests/mmif-examples/others/image.json",
77
+ "tests/mmif-examples/others/video-transcript-sentencesplit.json",
78
+ "tests/mmif-examples/others/video-transcript-demux-fa.short.json",
79
+ "tests/mmif-examples/others/slate-detect.json",
80
+ "tests/mmif-examples/others/image-box.json",
81
+ "tests/mmif-examples/others/slate-reco.json",
82
+ "tests/mmif-examples/others/video-transcript-demux.json",
83
+ "tests/mmif-examples/others/image-box-ocr.json",
84
+ "tests/mmif-examples/others/video-demux.json",
85
+ "tests/mmif-examples/bars-tones-slates/raw.json",
86
+ "tests/mmif-examples/east-tesseract-typing/raw.json",
87
+ "mmif_docloc_http/__init__.py",
88
+ ".github/workflows/issue-close.yml",
89
+ ".github/workflows/codecov.yml",
90
+ ".github/workflows/release-check.yml",
91
+ ".github/workflows/issue-infra-project.yml",
92
+ ".github/workflows/issue-assign.yml",
93
+ ".github/workflows/publish.yml"
94
+ ]
95
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.5.2",
3
+ "distance": 0,
4
+ "node": "g1b794b4741b27b9999c5cd9fec00d46bef6d5d8e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-07"
8
+ }
@@ -1451,7 +1451,57 @@ class TestDocument(unittest.TestCase):
1451
1451
  'author'))
1452
1452
  self.assertEqual('you', list(mmif_roundtrip2.views.get_last_contentful_view().get_annotations(AnnotationTypes.Annotation))[
1453
1453
  0].get_property('author'))
1454
-
1454
+
1455
+ def test_view_id_counts_rebuilt_on_deserialize(self):
1456
+ # regression for issue #393: `_id_counts` is not serialized, so after
1457
+ # deserialization the next generated id must not collide with an existing
1458
+ # one already present in the view.
1459
+ mmif = Mmif(validate=False)
1460
+ doc = Document(); doc.at_type = DocumentTypes.VideoDocument
1461
+ doc.id = 'd1'; doc.location = 'file:///v.mp4'
1462
+ mmif.add_document(doc)
1463
+ v = mmif.new_view()
1464
+ v.metadata.app = tester_appname
1465
+ a1 = v.new_annotation(AnnotationTypes.Annotation, document='d1', frameCount=10)
1466
+ self.assertTrue(a1.id.endswith('an_1'))
1467
+ # round-trip: the reloaded view still contains `an_1` but starts with an
1468
+ # empty counter unless rebuilt
1469
+ v2 = Mmif(mmif.serialize())[v.id]
1470
+ a2 = v2.new_annotation(AnnotationTypes.Annotation, document='d1', duration=20)
1471
+ self.assertTrue(a2.id.endswith('an_2')) # must not regenerate an_1
1472
+
1473
+ def test_capital_annotation_no_collision_across_apps(self):
1474
+ # regression for issue #393: a downstream app re-deriving a document
1475
+ # property with a *different* value must not crash on serialize; the
1476
+ # distinct value is recorded as an additional capital annotation
1477
+ # (value-based de-dup preserves both assertions), not a colliding one.
1478
+ m = Mmif(validate=False)
1479
+ d = Document()
1480
+ d.at_type = DocumentTypes.VideoDocument
1481
+ d.id = 'd1'
1482
+ d.location = 'file:///v.mp4'
1483
+ m.add_document(d)
1484
+ v1 = m.new_view(); v1.metadata.app = 'http://app/producer'
1485
+ v1.new_annotation(AnnotationTypes.TimeFrame, label='bars')
1486
+ d.add_property('frameCount', 51248.0)
1487
+ d.add_property('duration', 1709977.0)
1488
+
1489
+ # downstream app loads it and re-derives a *different* duration value
1490
+ m2 = Mmif(m.serialize())
1491
+ d2 = m2['d1']
1492
+ v2 = m2.new_view(); v2.metadata.app = 'http://app/consumer'
1493
+ v2.new_annotation(AnnotationTypes.TimeFrame, label='caption')
1494
+ d2.add_property('frameCount', 51248) # same value -> de-duped away
1495
+ d2.add_property('duration', 1709975) # different value -> new annotation
1496
+ out = Mmif(m2.serialize()) # must not raise KeyError
1497
+
1498
+ caps = [a for view in out.views
1499
+ for a in view.get_annotations(AnnotationTypes.Annotation)]
1500
+ durations = {a.get_property('duration') for a in caps if 'duration' in a}
1501
+ # both distinct duration assertions coexist; neither is lost or overwritten
1502
+ self.assertEqual(2, len(caps))
1503
+ self.assertEqual({1709977.0, 1709975}, durations)
1504
+
1455
1505
  def test_capital_annotation_generation_viewfinder(self):
1456
1506
  mmif = Mmif(validate=False)
1457
1507
  for i in range(1, 3):
File without changes
File without changes
File without changes
File without changes
File without changes