mmif-python 1.1.1__tar.gz → 1.2.0__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 (50) hide show
  1. {mmif_python-1.1.1/mmif_python.egg-info → mmif_python-1.2.0}/PKG-INFO +1 -2
  2. mmif_python-1.2.0/VERSION +1 -0
  3. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/serialize/annotation.py +100 -42
  4. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/serialize/mmif.py +121 -57
  5. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/serialize/model.py +192 -31
  6. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/serialize/view.py +93 -20
  7. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/cli/__init__.py +1 -0
  8. mmif_python-1.2.0/mmif/utils/cli/describe.py +333 -0
  9. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/cli/rewind.py +6 -6
  10. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/cli/source.py +4 -9
  11. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/sequence_helper.py +5 -5
  12. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/text_document_helper.py +9 -0
  13. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/video_document_helper.py +11 -6
  14. mmif_python-1.2.0/mmif/ver/__init__.py +2 -0
  15. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/vocabulary/base_types.py +11 -0
  16. {mmif_python-1.1.1 → mmif_python-1.2.0/mmif_python.egg-info}/PKG-INFO +1 -2
  17. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif_python.egg-info/SOURCES.txt +1 -0
  18. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif_python.egg-info/requires.txt +0 -1
  19. {mmif_python-1.1.1 → mmif_python-1.2.0}/requirements.txt +1 -1
  20. {mmif_python-1.1.1 → mmif_python-1.2.0}/tests/test_serialize.py +261 -34
  21. {mmif_python-1.1.1 → mmif_python-1.2.0}/tests/test_utils_cli.py +4 -4
  22. mmif_python-1.1.1/VERSION +0 -1
  23. mmif_python-1.1.1/mmif/ver/__init__.py +0 -2
  24. {mmif_python-1.1.1 → mmif_python-1.2.0}/LICENSE +0 -0
  25. {mmif_python-1.1.1 → mmif_python-1.2.0}/MANIFEST.in +0 -0
  26. {mmif_python-1.1.1 → mmif_python-1.2.0}/README.md +0 -0
  27. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/__init__.py +0 -0
  28. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/res/__init__.py +0 -0
  29. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/res/clams.vocabulary.yaml +0 -0
  30. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/res/do-not-edit.txt +0 -0
  31. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/res/mmif.json +0 -0
  32. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/serialize/__init__.py +0 -0
  33. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/__init__.py +0 -0
  34. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/utils/timeunit_helper.py +0 -0
  35. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/ver/do-not-edit.txt +0 -0
  36. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/vocabulary/__init__.py +0 -0
  37. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/vocabulary/annotation_types.py +0 -0
  38. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/vocabulary/do-not-edit.txt +0 -0
  39. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif/vocabulary/document_types.py +0 -0
  40. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif_docloc_http/__init__.py +0 -0
  41. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif_python.egg-info/dependency_links.txt +0 -0
  42. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif_python.egg-info/entry_points.txt +0 -0
  43. {mmif_python-1.1.1 → mmif_python-1.2.0}/mmif_python.egg-info/top_level.txt +0 -0
  44. {mmif_python-1.1.1 → mmif_python-1.2.0}/requirements.cv +0 -0
  45. {mmif_python-1.1.1 → mmif_python-1.2.0}/requirements.seq +0 -0
  46. {mmif_python-1.1.1 → mmif_python-1.2.0}/setup.cfg +0 -0
  47. {mmif_python-1.1.1 → mmif_python-1.2.0}/setup.py +0 -0
  48. {mmif_python-1.1.1 → mmif_python-1.2.0}/tests/test_utils.py +0 -0
  49. {mmif_python-1.1.1 → mmif_python-1.2.0}/tests/test_versioncompat.py +0 -0
  50. {mmif_python-1.1.1 → mmif_python-1.2.0}/tests/test_vocab.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mmif-python
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
5
5
  Home-page: https://mmif.clams.ai
6
6
  Author: Brandeis Lab for Linguistics and Computation
@@ -11,7 +11,6 @@ Classifier: Programming Language :: Python :: 3 :: Only
11
11
  Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: deepdiff>5
15
14
  Requires-Dist: orderly-set==5.3.*
16
15
  Requires-Dist: jsonschema
17
16
  Provides-Extra: seq
@@ -0,0 +1 @@
1
+ 1.2.0
@@ -74,10 +74,11 @@ class Annotation(MmifObject):
74
74
 
75
75
  def _cache_alignment(self, alignment_ann: 'Annotation', alignedto_ann: 'Annotation') -> None:
76
76
  """
77
- Cache alignment information. This cache will not be serialized.
78
-
77
+ Cache alignment information. This cache will not be serialized.
78
+
79
79
  :param alignment_ann: the Alignment annotation that has this annotation on one side
80
80
  :param alignedto_ann: the annotation that this annotation is aligned to (other side of Alignment)
81
+ :return: None
81
82
  """
82
83
  self._alignments[alignment_ann] = alignedto_ann
83
84
 
@@ -228,7 +229,7 @@ class Annotation(MmifObject):
228
229
  value: Union[PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]) -> None:
229
230
  """
230
231
  Adds a property to the annotation's properties.
231
-
232
+
232
233
  :param name: the name of the property
233
234
  :param value: the property's desired value
234
235
  :return: None
@@ -256,18 +257,41 @@ class Annotation(MmifObject):
256
257
 
257
258
  def get(self, prop_name: str, default=None):
258
259
  """
259
- A getter for Annotation, will search for a property by its name,
260
- and return the value if found, or the default value if not found.
261
- This is designed to allow for directly accessing properties without
262
- having to go through the properties object, or view-level
263
- annotation metadata (common properties) encoded in the
264
- ``view.metadata.contains`` dict. Note that the regular properties
265
- will take the priority over the view-level common properties when
266
- there are name conflicts.
267
-
268
- :param prop_name: the name of the property to get
269
- :param default: the value to return if the property is not found
270
- :return: the value of the property
260
+ Safe property access with optional default value.
261
+
262
+ Searches for an annotation property by name and returns its value,
263
+ or a default value if not found. This method searches in multiple
264
+ locations with the following priority:
265
+
266
+ 1. Direct properties (in ``annotation.properties``)
267
+ 2. Ephemeral properties (view-level metadata from ``contains``)
268
+ 3. Special fields (``@type``, ``properties``)
269
+
270
+ This allows convenient access to properties without explicitly
271
+ checking the ``properties`` object or view-level metadata.
272
+
273
+ :param prop_name: The name of the property to retrieve
274
+ :param default: The value to return if the property is not found (default: None)
275
+ :return: The property value, or the default value if not found
276
+
277
+ Examples
278
+ --------
279
+ .. code-block:: python
280
+
281
+ # Access annotation properties:
282
+ label = annotation.get('label', default='unknown')
283
+ start_time = annotation.get('start', default=0)
284
+
285
+ # Access @type:
286
+ at_type = annotation.get('@type')
287
+
288
+ # Safe access with custom default:
289
+ targets = annotation.get('targets', default=[])
290
+
291
+ See Also
292
+ --------
293
+ __getitem__ : Direct property access that raises KeyError when not found
294
+ get_property : Alias for this method
271
295
  """
272
296
  try:
273
297
  return self.__getitem__(prop_name)
@@ -336,29 +360,32 @@ class Document(Annotation):
336
360
  ) -> None:
337
361
  """
338
362
  Adds a property to the document's properties.
339
-
340
- Unlike the parent :class:`Annotation` class, added properties of a
341
- ``Document`` object can be lost during serialization unless it belongs
342
- to somewhere in a ``Mmif`` object. This is because we want to keep
343
- ``Document`` object as "read-only" as possible. Thus, if you want to add
344
- a property to a ``Document`` object,
345
-
346
- * add the document to a ``Mmif`` object (either in the documents list or
363
+
364
+ Unlike the parent :class:`Annotation` class, added properties of a
365
+ ``Document`` object can be lost during serialization unless it belongs
366
+ to somewhere in a ``Mmif`` object. This is because we want to keep
367
+ ``Document`` object as "read-only" as possible. Thus, if you want to add
368
+ a property to a ``Document`` object,
369
+
370
+ * add the document to a ``Mmif`` object (either in the documents list or
347
371
  in a view from the views list), or
348
372
  * directly write to ``Document.properties`` instead of using this method
349
- (which is not recommended).
350
-
351
- With the former method, the SDK will record the added property as a
352
- `Annotation` annotation object, separate from the original `Document`
373
+ (which is not recommended).
374
+
375
+ With the former method, the SDK will record the added property as a
376
+ `Annotation` annotation object, separate from the original `Document`
353
377
  object. See :meth:`.Mmif.generate_capital_annotations()` for more.
354
-
378
+
355
379
  A few notes to keep in mind:
356
-
357
- #. You can't overwrite an existing property of a ``Document`` object.
358
- #. A MMIF can have multiple ``Annotation`` objects with the same
380
+
381
+ #. You can't overwrite an existing property of a ``Document`` object.
382
+ #. A MMIF can have multiple ``Annotation`` objects with the same
359
383
  property name but different values. When this happens, the SDK will
360
- only keep the latest value (in order of appearances in views list) of
384
+ only keep the latest value (in order of appearances in views list) of
361
385
  the property, effectively overwriting the previous values.
386
+
387
+ :param name: the name of the property
388
+ :param value: the property's desired value (note: Document accepts fewer value types than Annotation)
362
389
  """
363
390
  # we don't checking if this k-v already exists in _original (new props) or _ephemeral (read from existing MMIF)
364
391
  # because it is impossible to keep the _original updated when a new annotation is added (via `new_annotation`)
@@ -378,13 +405,44 @@ class Document(Annotation):
378
405
 
379
406
  def get(self, prop_name, default=None):
380
407
  """
381
- A special getter for Document properties. The major difference from
382
- the super class's :py:meth:`Annotation.get` method is that Document
383
- class has one more set of *"pending"* properties, that are added after
384
- the Document object is created and will be serialized as a separate
385
- :py:class:`Annotation` object of which ``@type = Annotation``. The
386
- pending properties will take the priority over the regular properties
387
- when there are conflicts.
408
+ Safe property access with optional default value for Document objects.
409
+
410
+ Searches for a document property by name and returns its value, or a
411
+ default value if not found. Documents have a more complex property
412
+ hierarchy than regular annotations:
413
+
414
+ Priority order (highest to lowest):
415
+ 1. Special fields ('id', 'location')
416
+ 2. Pending properties (added after creation, to be serialized as ``Annotation`` objects)
417
+ 3. Ephemeral properties (from existing ``Annotation`` annotations or view metadata)
418
+ 4. Original properties (in ``document.properties``)
419
+
420
+ This allows convenient access to all document properties regardless of
421
+ where they're stored internally.
422
+
423
+ :param prop_name: The name of the property to retrieve
424
+ :param default: The value to return if the property is not found (default: None)
425
+ :return: The property value, or the default value if not found
426
+
427
+ Examples
428
+ --------
429
+ .. code-block:: python
430
+
431
+ # Access document properties:
432
+ mime = document.get('mime', default='application/octet-stream')
433
+ location = document.get('location')
434
+
435
+ # Access properties added after creation (pending):
436
+ author = document.get('author', default='anonymous')
437
+ publisher = document.get('publisher')
438
+
439
+ # Access ephemeral properties from Annotation objects:
440
+ sentiment = document.get('sentiment', default='neutral')
441
+
442
+ See Also
443
+ --------
444
+ add_property : Add a new property to the document
445
+ Mmif.generate_capital_annotations : How pending properties are serialized
388
446
  """
389
447
  if prop_name == 'id':
390
448
  # because all three dicts have `id` key as required field, we need
@@ -399,7 +457,7 @@ class Document(Annotation):
399
457
  elif prop_name in self._props_ephemeral:
400
458
  return self._props_ephemeral[prop_name]
401
459
  else:
402
- return super().get(prop_name)
460
+ return super().get(prop_name, default)
403
461
 
404
462
  get_property = get
405
463
 
@@ -559,8 +617,8 @@ class DocumentProperties(AnnotationProperties):
559
617
  self.location = input_dict.pop("location")
560
618
  super()._deserialize(input_dict)
561
619
 
562
- def _serialize(self, alt_container: Optional[Dict] = None) -> dict:
563
- serialized = super()._serialize()
620
+ def _serialize(self, *args, **kwargs) -> dict:
621
+ serialized = super()._serialize(**kwargs)
564
622
  if "location_" in serialized:
565
623
  serialized["location"] = serialized.pop("location_")
566
624
  return serialized
@@ -2,6 +2,11 @@
2
2
  The :mod:`mmif` module contains the classes used to represent a full MMIF
3
3
  file as a live Python object.
4
4
 
5
+ The :class:`Mmif` class is a high-level container that provides convenient
6
+ string-based access to documents, views, and annotations via ``mmif[id]``.
7
+ The underlying ``documents`` and ``views`` attributes are list-like collections
8
+ that use integer indexing; use container-level access for ID-based lookups.
9
+
5
10
  See the specification docs and the JSON Schema file for more information.
6
11
  """
7
12
 
@@ -139,8 +144,35 @@ class Mmif(MmifObject):
139
144
  """
140
145
  MmifObject that represents a full MMIF file.
141
146
 
147
+ This is a high-level container object that provides convenient string-based
148
+ access to documents, views, and annotations using their IDs. The underlying
149
+ collections (``documents`` and ``views``) are list-like and use integer
150
+ indexing, but Mmif itself accepts string IDs for convenient access.
151
+
142
152
  :param mmif_obj: the JSON data
143
153
  :param validate: whether to validate the data against the MMIF JSON schema.
154
+
155
+ Examples
156
+ --------
157
+ Accessing objects by ID (high-level, convenient):
158
+
159
+ .. code-block:: python
160
+
161
+ mmif = Mmif(mmif_json)
162
+ doc = mmif['m1'] # Get document by ID
163
+ view = mmif['v1'] # Get view by ID
164
+ ann = mmif['v1:a1'] # Get annotation by long-form ID
165
+
166
+ # Safe access with default:
167
+ doc = mmif.get('m99', default=None)
168
+
169
+ Accessing via underlying lists (positional access):
170
+
171
+ .. code-block:: python
172
+
173
+ first_doc = mmif.documents[0] # First document
174
+ last_view = mmif.views[-1] # Last view
175
+ all_views = mmif.views[1:4] # Slice of views
144
176
  """
145
177
 
146
178
  def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None, *, validate: bool = True) -> None:
@@ -179,17 +211,18 @@ class Mmif(MmifObject):
179
211
  json_str = json.loads(json_str)
180
212
  jsonschema.validators.validate(json_str, schema)
181
213
 
182
- def serialize(self, pretty: bool = False, sanitize: bool = False, autogenerate_capital_annotations=True) -> str:
214
+ def serialize(self, sanitize: bool = False, autogenerate_capital_annotations: bool = True, **kwargs) -> str:
183
215
  """
184
216
  Serializes the MMIF object to a JSON string.
185
217
 
186
- :param sanitize: If True, performs some sanitization of before returning
218
+ :param sanitize: If True, performs some sanitization of before returning
187
219
  the JSON string. See :meth:`sanitize` for details.
188
- :param autogenerate_capital_annotations: If True, automatically convert
189
- any "pending" temporary properties from `Document` objects to
190
- `Annotation` objects. See :meth:`generate_capital_annotations` for
220
+ :param autogenerate_capital_annotations: If True, automatically convert
221
+ any "pending" temporary properties from `Document` objects to
222
+ `Annotation` objects. See :meth:`generate_capital_annotations` for
191
223
  details.
192
- :param pretty: If True, returns string representation with indentation.
224
+ :param kwargs: Keyword arguments to pass to the parent's ``serialize``
225
+ method (e.g., ``pretty=True``, ``include_context=False``).
193
226
  :return: JSON string of the MMIF object.
194
227
  """
195
228
  if autogenerate_capital_annotations:
@@ -197,7 +230,7 @@ class Mmif(MmifObject):
197
230
  # sanitization should be done after `Annotation` annotations are generated
198
231
  if sanitize:
199
232
  self.sanitize()
200
- return super().serialize(pretty)
233
+ return super().serialize(**kwargs)
201
234
 
202
235
  def _deserialize(self, input_dict: dict) -> None:
203
236
  """
@@ -266,8 +299,8 @@ class Mmif(MmifObject):
266
299
  ## caching alignments
267
300
  if all(map(lambda x: x in alignment_ann.properties, ('source', 'target'))):
268
301
  try:
269
- source_ann = self[alignment_ann.get('source')]
270
- target_ann = self[alignment_ann.get('target')]
302
+ source_ann = self.__getitem__(alignment_ann.get('source'))
303
+ target_ann = self.__getitem__(alignment_ann.get('target'))
271
304
  if isinstance(source_ann, Annotation) and isinstance(target_ann, Annotation):
272
305
  source_ann._cache_alignment(alignment_ann, target_ann)
273
306
  target_ann._cache_alignment(alignment_ann, source_ann)
@@ -408,13 +441,13 @@ class Mmif(MmifObject):
408
441
  """
409
442
  Appends a View object to the views list.
410
443
 
411
- Fails if there is already a view with the same ID or a document
444
+ Fails if there is already a view with the same ID or a document
412
445
  with the same ID in the MMIF object.
413
446
 
414
- :param view: the Document object to add
447
+ :param view: the View object to add
415
448
  :param overwrite: if set to True, will overwrite
416
449
  an existing view with the same ID
417
- :raises KeyError: if ``overwrite`` is set to False and existing
450
+ :raises KeyError: if ``overwrite`` is set to False and existing
418
451
  object (document or view) with the same ID exists
419
452
  :return: None
420
453
  """
@@ -503,6 +536,7 @@ class Mmif(MmifObject):
503
536
  Only top-level documents have locations, so we only check them.
504
537
 
505
538
  :param m_type: the type to search for
539
+ :param path_only: if True, returns resolved file system paths instead of location URIs
506
540
  :return: a list of the values of the location fields in the corresponding documents
507
541
  """
508
542
  docs = [document for document in self.documents if document.is_type(m_type) and document.location is not None]
@@ -516,6 +550,7 @@ class Mmif(MmifObject):
516
550
  Method to get the location of *first* document of given type.
517
551
 
518
552
  :param m_type: the type to search for
553
+ :param path_only: if True, returns resolved file system path instead of location URI
519
554
  :return: the value of the location field in the corresponding document
520
555
  """
521
556
  # TODO (krim @ 8/10/20): Is returning the first location desirable?
@@ -568,6 +603,8 @@ class Mmif(MmifObject):
568
603
  """
569
604
  Finds views where alignments between two given annotation types occurred.
570
605
 
606
+ :param at_type1: the first annotation type to search for alignments
607
+ :param at_type2: the second annotation type to search for alignments
571
608
  :return: a dict that keyed by view IDs (str) and has lists of alignment Annotation objects as values.
572
609
  """
573
610
  v_and_a = {}
@@ -586,7 +623,7 @@ class Mmif(MmifObject):
586
623
  aligned_types = set()
587
624
  for ann_id in [alignment['target'], alignment['source']]:
588
625
  ann_id = cast(str, ann_id)
589
- aligned_type = cast(Annotation, self[ann_id]).at_type
626
+ aligned_type = cast(Annotation, self.__getitem__(ann_id)).at_type
590
627
  aligned_types.add(aligned_type)
591
628
  aligned_types = list(aligned_types) # because membership check for sets also checks hash() values
592
629
  if len(aligned_types) == 2 and at_type1 in aligned_types and at_type2 in aligned_types:
@@ -691,8 +728,8 @@ class Mmif(MmifObject):
691
728
  """
692
729
  Finds annotations that are anchored between the given time points.
693
730
 
694
- :param start: the start time point in the unit of `input_unit`
695
- :param end: the end time point in the unit of `input_unit`
731
+ :param start: the start time point
732
+ :param end: the end time point
696
733
  :param time_unit: the unit of the input time points. Default is `ms`.
697
734
  :param at_types: a list of annotation types to filter with. Any type in this list will be included in the return.
698
735
  :return: an iterator of Annotation objects that are anchored between the given time points
@@ -725,12 +762,12 @@ class Mmif(MmifObject):
725
762
  def _get_linear_anchor_point(self, ann: Annotation, targets_sorted=False, start: bool = True) -> Union[int, float]:
726
763
  # TODO (krim @ 2/5/24): Update the return type once timeunits are unified to `ms` as integers (https://github.com/clamsproject/mmif/issues/192)
727
764
  """
728
- Retrieves the anchor point of the annotation. Currently, this method only supports linear anchors,
765
+ Retrieves the anchor point of the annotation. Currently, this method only supports linear anchors,
729
766
  namely time and text, hence does not work with spatial anchors (polygons or video-object).
730
-
767
+
731
768
  :param ann: An Annotation object that has a linear anchor point. Namely, some subtypes of `Region` vocabulary type.
732
- :param start: If True, returns the start anchor point. Otherwise, returns the end anchor point. N/A for `timePoint` anchors.
733
769
  :param targets_sorted: If True, the method will assume that the targets are sorted in the order of the anchor points.
770
+ :param start: If True, returns the start anchor point. Otherwise, returns the end anchor point. N/A for `timePoint` anchors.
734
771
  :return: the anchor point of the annotation. 1d for linear regions (time, text)
735
772
  """
736
773
  props = ann.properties
@@ -745,10 +782,10 @@ class Mmif(MmifObject):
745
782
  point = math.inf if start else -1
746
783
  comp = min if start else max
747
784
  for target_id in ann.get_property('targets'):
748
- point = comp(point, self._get_linear_anchor_point(self[target_id], start=start))
785
+ point = comp(point, self._get_linear_anchor_point(self.__getitem__(target_id), start=start))
749
786
  return point
750
787
  target_id = ann.get_property('targets')[0 if start else -1]
751
- return self._get_linear_anchor_point(self[target_id], start=start)
788
+ return self._get_linear_anchor_point(self.__getitem__(target_id), start=start)
752
789
  elif (start and 'start' in props) or (not start and 'end' in props):
753
790
  return ann.get_property('start' if start else 'end')
754
791
  else:
@@ -766,50 +803,77 @@ class Mmif(MmifObject):
766
803
  """
767
804
  return self._get_linear_anchor_point(annotation, start=False)
768
805
 
769
- def __getitem__(self, item: str) \
770
- -> Union[Document, View, Annotation, MmifMetadata, DocumentsList, ViewsList]:
806
+ def __getitem__(self, item: str) -> Union[Document, View, Annotation, MmifMetadata]:
771
807
  """
772
- index ([]) implementation for Mmif. This will try to find any object, given an identifier or an immediate
773
- attribute name. When nothing is found, this will raise an error rather than returning a None
808
+ High-level string-based access to MMIF objects by their IDs.
809
+
810
+ This method provides convenient access to documents, views, and annotations
811
+ using their string identifiers. For long-form annotation IDs (format: ``V:A``),
812
+ performs a two-level search through the specified view.
813
+
814
+ Note: This is a high-level convenience method on the Mmif container itself.
815
+ The underlying ``documents`` and ``views`` collections are list-like and
816
+ only support integer indexing.
774
817
 
775
- :raises KeyError: if the item is not found or if the search results are ambiguous
776
- :param item: an attribute name or an object identifier (a document ID, a view ID, or an annotation ID). When
777
- annotation ID is given as a "short" ID (without view ID prefix), the method will try to find a
778
- match from the first view, and return immediately if found.
779
- :return: the object searched for
780
- :raise KeyError: if the item is not found or multiple objects are found with the same ID
818
+ :param item: An object identifier:
819
+ - Document ID (e.g., 'm1', 'd1')
820
+ - View ID (e.g., 'v1', 'v_0')
821
+ - Annotation ID in long form (e.g., 'v1:a1', 'v1:tf1')
822
+ - Attribute name (e.g., 'metadata', 'documents', 'views')
823
+ :return: The requested Document, View, Annotation, or attribute object
824
+ :raises KeyError: If the item is not found
825
+
826
+ Examples
827
+ --------
828
+ High-level access by ID:
829
+
830
+ .. code-block:: python
831
+
832
+ mmif = Mmif(mmif_json)
833
+
834
+ # Access documents:
835
+ doc = mmif['m1'] # Returns Document with ID 'm1'
836
+
837
+ # Access views:
838
+ view = mmif['v1'] # Returns View with ID 'v1'
839
+
840
+ # Access annotations (long-form ID):
841
+ ann = mmif['v1:a1'] # Returns Annotation from view v1
842
+
843
+ # Access attributes:
844
+ metadata = mmif['metadata'] # Returns MmifMetadata object
845
+
846
+ # Will raise KeyError:
847
+ doc = mmif['nonexistent'] # KeyError!
848
+
849
+ For list-style positional access, use the underlying collections:
850
+
851
+ .. code-block:: python
852
+
853
+ first_doc = mmif.documents[0] # Integer index
854
+ second_view = mmif.views[1] # Integer index
855
+
856
+ See Also
857
+ --------
858
+ get : Safe access with default value instead of raising KeyError
781
859
  """
782
860
  if item in self._named_attributes():
783
- return self.__dict__[item]
861
+ return self.__dict__.__getitem__(item)
784
862
  if self.id_delimiter in item:
785
863
  vid, _ = item.split(self.id_delimiter, 1)
786
- return self.views[vid].annotations[item]
864
+ view = self.views._items.get(vid)
865
+ if view is None:
866
+ raise KeyError(f"View with ID {vid} not found in the MMIF object.")
867
+ ann = view.annotations._items.get(item)
868
+ if ann is None:
869
+ raise KeyError(f"Annotation with ID {item} not found in the MMIF object.")
870
+ return ann
787
871
  else:
788
872
  # search for document first, then views
789
873
  # raise KeyError if nothing is found
790
- try:
791
- return self.documents.__getitem__(item)
792
- except KeyError:
793
- try:
794
- return self.views.__getitem__(item)
795
- except KeyError:
874
+ ret = self.documents._items.get(item)
875
+ if ret is None:
876
+ ret = self.views._items.get(item)
877
+ if ret is None:
796
878
  raise KeyError(f"Object with ID {item} not found in the MMIF object. ")
797
-
798
- def get(self, obj_id, default=None):
799
- """
800
- High-level getter for Mmif. This will try to find any object, given
801
- an identifier or an immediate attribute name. When nothing is found,
802
- this will return a default value instead of raising an error.
803
-
804
- :param obj_id: an immediate attribute name or an object identifier
805
- (a document ID, a view ID, or an annotation ID). When
806
- annotation ID is given as a "short" ID (without view
807
- ID prefix), the method will try to find a match from
808
- the first view, and return immediately if found.
809
- :param default: the default value to return if none is found
810
- :return: the object searched for or the default value
811
- """
812
- try:
813
- return self.__getitem__(obj_id)
814
- except KeyError:
815
- return default
879
+ return ret