mmif-python 1.0.19__tar.gz → 1.1.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 (51) hide show
  1. {mmif_python-1.0.19/mmif_python.egg-info → mmif_python-1.1.0}/PKG-INFO +17 -4
  2. mmif_python-1.1.0/VERSION +1 -0
  3. mmif_python-1.1.0/mmif/__init__.py +66 -0
  4. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/res/clams.vocabulary.yaml +130 -6
  5. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/serialize/annotation.py +88 -38
  6. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/serialize/mmif.py +111 -111
  7. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/serialize/model.py +3 -2
  8. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/serialize/view.py +113 -44
  9. mmif_python-1.1.0/mmif/utils/cli/__init__.py +3 -0
  10. mmif_python-1.1.0/mmif/utils/cli/rewind.py +113 -0
  11. mmif_python-1.1.0/mmif/utils/cli/source.py +293 -0
  12. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/utils/sequence_helper.py +1 -1
  13. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/utils/text_document_helper.py +2 -1
  14. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/utils/timeunit_helper.py +14 -3
  15. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/utils/video_document_helper.py +81 -33
  16. mmif_python-1.1.0/mmif/ver/__init__.py +2 -0
  17. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/vocabulary/annotation_types.py +27 -14
  18. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/vocabulary/base_types.py +12 -1
  19. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/vocabulary/document_types.py +2 -2
  20. {mmif_python-1.0.19 → mmif_python-1.1.0/mmif_python.egg-info}/PKG-INFO +17 -4
  21. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif_python.egg-info/SOURCES.txt +9 -4
  22. mmif_python-1.1.0/mmif_python.egg-info/entry_points.txt +2 -0
  23. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif_python.egg-info/requires.txt +2 -0
  24. {mmif_python-1.0.19 → mmif_python-1.1.0}/requirements.cv +1 -0
  25. mmif_python-1.1.0/requirements.txt +3 -0
  26. {mmif_python-1.0.19 → mmif_python-1.1.0}/setup.py +7 -2
  27. {mmif_python-1.0.19 → mmif_python-1.1.0}/tests/test_serialize.py +142 -83
  28. {mmif_python-1.0.19 → mmif_python-1.1.0}/tests/test_utils.py +49 -21
  29. mmif_python-1.1.0/tests/test_utils_cli.py +170 -0
  30. {mmif_python-1.0.19 → mmif_python-1.1.0}/tests/test_vocab.py +3 -3
  31. mmif_python-1.0.19/VERSION +0 -1
  32. mmif_python-1.0.19/mmif/__init__.py +0 -17
  33. mmif_python-1.0.19/mmif/ver/__init__.py +0 -2
  34. mmif_python-1.0.19/requirements.txt +0 -2
  35. {mmif_python-1.0.19 → mmif_python-1.1.0}/LICENSE +0 -0
  36. {mmif_python-1.0.19 → mmif_python-1.1.0}/MANIFEST.in +0 -0
  37. {mmif_python-1.0.19 → mmif_python-1.1.0}/README.md +0 -0
  38. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/res/__init__.py +0 -0
  39. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/res/do-not-edit.txt +0 -0
  40. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/res/mmif.json +0 -0
  41. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/serialize/__init__.py +0 -0
  42. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/utils/__init__.py +0 -0
  43. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/ver/do-not-edit.txt +0 -0
  44. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/vocabulary/__init__.py +0 -0
  45. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif/vocabulary/do-not-edit.txt +0 -0
  46. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif_docloc_http/__init__.py +0 -0
  47. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif_python.egg-info/dependency_links.txt +0 -0
  48. {mmif_python-1.0.19 → mmif_python-1.1.0}/mmif_python.egg-info/top_level.txt +0 -0
  49. {mmif_python-1.0.19 → mmif_python-1.1.0}/requirements.seq +0 -0
  50. {mmif_python-1.0.19 → mmif_python-1.1.0}/setup.cfg +0 -0
  51. {mmif_python-1.0.19 → mmif_python-1.1.0}/tests/test_versioncompat.py +0 -0
@@ -1,18 +1,19 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: mmif-python
3
- Version: 1.0.19
3
+ Version: 1.1.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
7
7
  Author-email: admin@clams.ai
8
8
  Classifier: Development Status :: 2 - Pre-Alpha
9
- Classifier: Intended Audience :: Developers
9
+ Classifier: Intended Audience :: Developers
10
10
  Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python :: 3 :: Only
12
- Requires-Python: >=3.8
12
+ Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: deepdiff>5
16
+ Requires-Dist: orderly-set==5.3.*
16
17
  Requires-Dist: jsonschema
17
18
  Provides-Extra: seq
18
19
  Requires-Dist: numpy; extra == "seq"
@@ -20,11 +21,23 @@ Provides-Extra: cv
20
21
  Requires-Dist: pillow; extra == "cv"
21
22
  Requires-Dist: opencv-python; extra == "cv"
22
23
  Requires-Dist: ffmpeg-python; extra == "cv"
24
+ Requires-Dist: wurlitzer; extra == "cv"
23
25
  Provides-Extra: dev
24
26
  Requires-Dist: pytest; extra == "dev"
25
27
  Requires-Dist: pytest-pep8; extra == "dev"
26
28
  Requires-Dist: pytest-cov; extra == "dev"
27
29
  Requires-Dist: pytype; extra == "dev"
30
+ Dynamic: author
31
+ Dynamic: author-email
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: license-file
37
+ Dynamic: provides-extra
38
+ Dynamic: requires-dist
39
+ Dynamic: requires-python
40
+ Dynamic: summary
28
41
 
29
42
  ## MultiMedia Interchange Format
30
43
  [MMIF](https://mmif.clams.ai) is a JSON(-LD)-based data format designed for transferring annotation data between computational analysis applications in [CLAMS project](https://clams.ai).
@@ -0,0 +1 @@
1
+ 1.1.0
@@ -0,0 +1,66 @@
1
+ from importlib.resources import files
2
+
3
+ import argparse
4
+ import importlib
5
+ import pkgutil
6
+ import sys
7
+
8
+ # DO NOT CHANGE THIS ORDER, important to prevent circular imports
9
+ from mmif.ver import __version__
10
+ from mmif.ver import __specver__
11
+ from mmif.vocabulary import *
12
+ from mmif.serialize import *
13
+ from mmif.utils.cli import rewind
14
+ from mmif.utils.cli import source
15
+
16
+ _res_pkg = 'res'
17
+ _ver_pkg = 'ver'
18
+ _vocabulary_pkg = 'vocabulary'
19
+ _schema_res_name = 'mmif.json'
20
+ version_template = "{} (based on MMIF spec: {})"
21
+
22
+
23
+ def get_mmif_json_schema():
24
+ return files(f'{__package__}.{_res_pkg}').joinpath(_schema_res_name).read_text()
25
+
26
+
27
+ def find_all_modules(pkgname):
28
+ parent = importlib.import_module(pkgname)
29
+ if not hasattr(parent, '__path__'):
30
+ raise ImportError(f"Error: '{pkgname}' is not a package.")
31
+ for importer, module, ispkg in pkgutil.walk_packages(parent.__path__, parent.__name__ + '.'):
32
+ if not ispkg: # Only process modules, not subpackages themselves
33
+ yield importlib.import_module(module)
34
+
35
+
36
+ def prep_argparser_and_subcmds():
37
+ parser = argparse.ArgumentParser()
38
+ parser.add_argument(
39
+ '-v', '--version',
40
+ action='version',
41
+ version=version_template.format(__version__, __specver__)
42
+ )
43
+ subparsers = parser.add_subparsers(title='sub-command', dest='subcmd')
44
+ return parser, subparsers
45
+
46
+
47
+ def cli():
48
+ parser, subparsers = prep_argparser_and_subcmds()
49
+ cli_modules = {}
50
+ for cli_module in find_all_modules('mmif.utils.cli'):
51
+ cli_module_name = cli_module.__name__.rsplit('.')[-1]
52
+ cli_modules[cli_module_name] = cli_module
53
+ subcmd_parser = cli_module.prep_argparser(add_help=False)
54
+ subparsers.add_parser(cli_module_name, parents=[subcmd_parser],
55
+ help=cli_module.describe_argparser()[0],
56
+ description=cli_module.describe_argparser()[1],
57
+ formatter_class=argparse.RawDescriptionHelpFormatter,
58
+ )
59
+ if len(sys.argv) == 1:
60
+ parser.print_help(sys.stderr)
61
+ sys.exit(1)
62
+ args = parser.parse_args()
63
+ if args.subcmd not in cli_modules:
64
+ parser.print_help(sys.stderr)
65
+ else:
66
+ cli_modules[args.subcmd].main(args)
@@ -72,11 +72,25 @@ properties:
72
72
  label:
73
73
  type: String
74
74
  description: >-
75
- A label given to this object by a classifier. The value must be a simple
76
- string value of the label and must be one of the values defined in the
77
- <code>labelset</code> or <code>labelsetUri</code> annotation metadata.
75
+ A label given to this object by classification. The value must be a
76
+ simple string value of the label and must be one of the values
77
+ defined in the <code>labelset</code> or <code>labelsetUri</code>
78
+ annotation metadata.
78
79
  <br><br>
79
- [Note] Annotations from a classifier app must have this property.
80
+ For example, for the <code>Sentence</code> subtype, this could
81
+ be used to indicate the type of sentence, such as "declarative",
82
+ "interrogative", "exclamatory", etc. For <code>NamedEntity</code>
83
+ subtype, this could be used to indicate the type of named entity,
84
+ such as "PER", "ORG", "LOC", "MISC" (following the CoNLL-2003 labels).
85
+ <br><br>
86
+ For non-linguistic annotations, for example for <code>TimeFrame<code>,
87
+ this could be used to indicate the type of the time frame, such as
88
+ "speech", "music", "noise", "bars-and-tones", etc, for acoustic
89
+ classification. Or "slate", "lower-third", "credits" for visual
90
+ classification of video frames.
91
+ <br><br>
92
+ [Note] Annotations from a type of classifier model must have this
93
+ property.
80
94
  classifications:
81
95
  type: Map from String to Number
82
96
  description: >-
@@ -110,7 +124,9 @@ metadata:
110
124
  description: >-
111
125
  Specifies which unit of time the measurement is based.
112
126
  Can be *seconds* or *milliseconds*, or in case of annotations on
113
- a VideoDocument, *frames*.
127
+ a VideoDocument, *frames*. If not specified, *milliseconds* (in whole numbers) is assumed.
128
+ <br><br>
129
+ [Note] This metadata is only relevant for time-based annotations.
114
130
 
115
131
  ---
116
132
 
@@ -174,6 +190,115 @@ description: >-
174
190
  pointing directly into primary data (by using start and end offsets) or
175
191
  by linking to one or more other Annotations with the targets property.
176
192
 
193
+ properties:
194
+ text:
195
+ type: String
196
+ description: The surface string in the primary data covered by this span.
197
+ ---
198
+
199
+ name: Token
200
+ parent: Span
201
+
202
+ similarTo:
203
+ - http://vocab.lappsgrid.org/Token
204
+
205
+ description: >-
206
+ A string of one or more characters that serves as an indivisible unit
207
+ for the purposes of morpho-syntactic labeling (part of speech tagging).
208
+
209
+ properties:
210
+ pos:
211
+ type: String or URI
212
+ description: Part-of-speech tag associated with the token.
213
+ lemma:
214
+ type: String or URI
215
+ description: >-
216
+ The root (base) form associated with the token. URI may point to a
217
+ lexicon entry.
218
+ orth:
219
+ type: String or URI
220
+ description: >-
221
+ Orthographic properties of the token such as LowerCase, UpperCase,
222
+ UpperInitial, etc. Ideally a URI referencing a pre-defined descriptor.
223
+
224
+ ---
225
+
226
+ name: Sentence
227
+ parent: Span
228
+
229
+ similarTo:
230
+ - http://vocab.lappsgrid.org/Sentence
231
+
232
+ description: >
233
+ A sequence of words capable of standing alone to make an assertion, ask a
234
+ question, or give a command, usually consisting of a subject and a predicate
235
+ containing a finite verb.
236
+
237
+ ---
238
+
239
+ name: Paragraph
240
+ parent: Span
241
+
242
+ similarTo:
243
+ - http://vocab.lappsgrid.org/Paragraph
244
+
245
+ description: >-
246
+ A division of a piece of writing, usually dealing with a single theme and
247
+ indicated by a new line, indentation, and/or numbering.
248
+
249
+ ---
250
+
251
+ name: NamedEntity
252
+ parent: Span
253
+
254
+ similarTo:
255
+ - http://vocab.lappsgrid.org/NamedEntity
256
+
257
+ description: >-
258
+ A phrase that clearly identifies an individual from others that have similar
259
+ attributes, such as the name of a person, organization, location, artifact,
260
+ etc. as well as temporal expressions.
261
+
262
+ ---
263
+
264
+ name: NounChunk
265
+ parent: Span
266
+
267
+ similarTo:
268
+ - http://vocab.lappsgrid.org/NounChunk
269
+
270
+ description: >-
271
+ The initial portion of a non-recursive noun phrase up to the head, including
272
+ determiners but not including postmodifying prepositional phrases or clauses.
273
+
274
+ ---
275
+
276
+ name: VerbChunk
277
+ parent: Span
278
+
279
+ similarTo:
280
+ - http://vocab.lappsgrid.org/VerbChunk
281
+
282
+ description: >-
283
+ Non-recursive verb groups, which include modals, auxiliary verbs, and medial
284
+ adverbs, and end at the head verb or predicate adjective.
285
+
286
+ properties:
287
+ tense:
288
+ type: String or URI
289
+ description: >-
290
+ Provides tense information for the verb. Example values include BeVBG,
291
+ BeVBN, FutCon, HaveVBN, Pas, PasCon, PasPer, PasPerCon, Per, Pre, PreCon,
292
+ PrePer, PrePerCon, SimFut, SimPas, SimPre, none
293
+ voice:
294
+ type: String or URI
295
+ description: >-
296
+ Indicates if the verb group is active or passive. Possible values include
297
+ ACTIVE, PASSIVE, or NONE
298
+ neg:
299
+ type: String or URI
300
+ description: Indicates whether or not the verb is negated. Values include YES, NO.
301
+
177
302
  ---
178
303
 
179
304
  name: TimeFrame
@@ -195,7 +320,6 @@ properties:
195
320
  No longer encouraged to use, instead <code>label</code> property
196
321
  should replace this property.
197
322
 
198
-
199
323
  ---
200
324
 
201
325
  name: Chapter
@@ -49,10 +49,9 @@ class Annotation(MmifObject):
49
49
  def __init__(self, anno_obj: Optional[Union[bytes, str, dict]] = None, *_) -> None:
50
50
  self._type: ThingTypesBase = ThingTypesBase('')
51
51
  # to store the parent view ID
52
- self._parent_view_id = ''
53
52
  self._props_ephemeral: AnnotationProperties = AnnotationProperties()
54
53
  self._alignments = {} # to hold alignment information (Alignment anno long_id -> aligned anno long_id)
55
- self.reserved_names.update(('_parent_view_id', '_props_ephemeral', '_alignments'))
54
+ self.reserved_names.update(('_props_ephemeral', '_alignments'))
56
55
  if not hasattr(self, 'properties'): # don't overwrite DocumentProperties on super() call
57
56
  self.properties: AnnotationProperties = AnnotationProperties()
58
57
  self._attribute_classes = {'properties': AnnotationProperties}
@@ -116,17 +115,18 @@ class Annotation(MmifObject):
116
115
  in `TimeFrame` and `BoundingBox` respectively.
117
116
  """
118
117
  prop_aliases = AnnotationTypes._prop_aliases.get(self._type.shortname, {})
119
- for alias_reprep, alias_group in prop_aliases.items():
118
+ for alias_rep, alias_group in prop_aliases.items():
120
119
  if key_to_add in alias_group:
121
120
  for alias in alias_group:
122
121
  if alias != key_to_add:
123
122
  self._props_ephemeral[alias] = val_to_add
124
123
  if alias in self.properties.keys():
125
- warning_msg = f'Found both "{key_to_add}" and "{alias}" in the properties of "{self.id}" annotation in "{self.parent}" view. '
126
- if alias == alias_reprep:
127
- warning_msg += f'However "{key_to_add}" is an alias of "{alias_reprep}".'
124
+ warning_msg = (f'Both "{key_to_add}" and "{alias}" are in the properties of "{self.id}", '
125
+ f'however ')
126
+ if alias == alias_rep:
127
+ warning_msg += f'"{key_to_add}" is an alias of "{alias_rep}".'
128
128
  else:
129
- warning_msg += f'However "{key_to_add}" and "{alias}" are boath aliases of "{alias_reprep}".'
129
+ warning_msg += f'"{key_to_add}" and "{alias}" are both aliases of "{alias_rep}".'
130
130
  warning_msg += f'Having two synonyms in the same annotation can cause unexpected behavior. '
131
131
  warnings.warn(warning_msg, UserWarning)
132
132
 
@@ -134,6 +134,8 @@ class Annotation(MmifObject):
134
134
  """
135
135
  Check if the @type of this object matches.
136
136
  """
137
+ if isinstance(at_type, str):
138
+ at_type = ThingTypesBase.from_str(at_type)
137
139
  return self.at_type == at_type
138
140
 
139
141
  @property
@@ -149,13 +151,28 @@ class Annotation(MmifObject):
149
151
 
150
152
  @property
151
153
  def parent(self) -> str:
152
- return self._parent_view_id
153
-
154
+ if self.id_delimiter in self.properties.id:
155
+ id_split = self.properties.id.split(self.id_delimiter)
156
+ if len(id_split) == 2:
157
+ return id_split[0]
158
+ raise ValueError(f'Annotation {self.id} does not have a parent view, or its ID does not follow the expected format. ')
159
+
154
160
  @parent.setter
155
- def parent(self, parent_view_id: str) -> None:
156
- # I want to make this to accept `View` object as an input too,
157
- # but import `View` will break the code due to circular imports
158
- self._parent_view_id = parent_view_id
161
+ def parent(self, parent_id: str) -> None:
162
+ """
163
+ .. deprecated:: 1.1.0
164
+ Will be removed in 2.0.0.
165
+ Setting parent ID is no longer allowed. Instead, the parent ID
166
+ should be prefixed to the annotation ID.
167
+
168
+ Sets the parent view ID of this annotation.
169
+ :param parent_id:
170
+ """
171
+ warnings.warn(
172
+ "Setting parent ID is deprecated and nothing happened. Use prefixed annotation ID instead.",
173
+ DeprecationWarning
174
+ )
175
+
159
176
 
160
177
  @property
161
178
  def id(self) -> str:
@@ -167,17 +184,27 @@ class Annotation(MmifObject):
167
184
 
168
185
  @property
169
186
  def long_id(self) -> str:
170
- if self.parent is not None and len(self.parent) > 0:
171
- return f"{self.parent}{self.id_delimiter}{self.id}"
172
- else:
173
- return self.id
187
+ warnings.warn(
188
+ 'long_id is deprecated. Use `id` instead. ', DeprecationWarning
189
+ )
190
+ return self.id
174
191
 
175
192
  @long_id.setter
176
193
  def long_id(self, long_id: str) -> None:
177
- if self.id_delimiter in long_id:
178
- self.parent, self.id = long_id.split(self.id_delimiter)
179
- else:
180
- self.id = long_id
194
+ warnings.warn(
195
+ 'long_id is deprecated. Use `id` instead. ', DeprecationWarning
196
+ )
197
+ self.id = long_id
198
+
199
+ @property
200
+ def _short_id(self) -> str:
201
+ # TODO (krim @ 6/27/25): DELETE THIS METHOD!
202
+ """
203
+ Method to directly get "short" form of the ID, not supposed to be used for general purpose, since
204
+ we want to force usage of "long" form ID when recording and referring annotations.
205
+ :return:
206
+ """
207
+ return self.properties.id
181
208
 
182
209
  @staticmethod
183
210
  def check_prop_value_is_simple_enough(
@@ -215,14 +242,7 @@ class Annotation(MmifObject):
215
242
  # f"(\"{name}\": \"{str(value)}\"")
216
243
  self._add_prop_aliases(name, value)
217
244
 
218
- def get(self, prop_name: str) -> Union['AnnotationProperties', PRMTV_TYPES, LIST_PRMTV, LIST_LIST_PRMTV, DICT_PRMTV, DICT_LIST_PRMTV]:
219
- """
220
- A special getter for Annotation properties. This is to allow for
221
- directly accessing properties without having to go through the
222
- properties object, or view-level annotation properties encoded in the
223
- ``view.metadata.contains`` dict. Note that the regular props will take
224
- the priority over the ephemeral props when there are conflicts.
225
- """
245
+ def __getitem__(self, prop_name: str):
226
246
  if prop_name in {'at_type', '@type'}:
227
247
  return str(self._type)
228
248
  elif prop_name == 'properties':
@@ -234,14 +254,31 @@ class Annotation(MmifObject):
234
254
  else:
235
255
  raise KeyError(f"Property {prop_name} does not exist in this annotation.")
236
256
 
257
+ def get(self, prop_name: str, default=None):
258
+ """
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
271
+ """
272
+ try:
273
+ return self.__getitem__(prop_name)
274
+ except KeyError:
275
+ return default
276
+
237
277
  get_property = get
238
278
 
239
- def __getitem__(self, prop_name: str):
240
- return self.get(prop_name)
241
-
242
279
  def __contains__(self, item):
243
280
  try:
244
- self.get(item)
281
+ self.__getitem__(item)
245
282
  return True
246
283
  except KeyError:
247
284
  return False
@@ -339,7 +376,7 @@ class Document(Annotation):
339
376
  else:
340
377
  super().add_property(name, value)
341
378
 
342
- def get(self, prop_name):
379
+ def get(self, prop_name, default=None):
343
380
  """
344
381
  A special getter for Document properties. The major difference from
345
382
  the super class's :py:meth:`Annotation.get` method is that Document
@@ -448,13 +485,21 @@ class AnnotationProperties(MmifObject, MutableMapping[str, T]):
448
485
  """
449
486
 
450
487
  def __delitem__(self, key: str) -> None:
488
+ frm = None
451
489
  for k in self.__iter__():
452
490
  if k == key:
453
- if k not in self._required_attributes:
454
- del self.__dict__[k]
455
- else:
491
+ if k in self._required_attributes:
456
492
  raise AttributeError(f'Cannot delete a required attribute "{key}"!')
457
- raise KeyError(f'Key "{key}" not found.')
493
+ elif k in self._unnamed_attributes:
494
+ frm = self._unnamed_attributes
495
+ break
496
+ else:
497
+ frm = self.__dict__
498
+ break
499
+ if frm is not None:
500
+ del frm[key]
501
+ else:
502
+ raise KeyError(f'Key "{key}" not found.')
458
503
 
459
504
  def __iter__(self) -> Iterator[str]:
460
505
  """
@@ -576,6 +621,11 @@ class DocumentProperties(AnnotationProperties):
576
621
  return "".join((parsed_location.netloc, parsed_location.path))
577
622
 
578
623
  def location_path(self) -> Optional[str]:
624
+ """
625
+ .. deprecated:: 1.0.2
626
+ Will be removed in 2.0.0.
627
+ Use :meth:`location_path_resolved` instead.
628
+ """
579
629
  warnings.warn('location_path() is deprecated. Use location_path_resolved() instead.', DeprecationWarning)
580
630
  return self.location_path_resolved()
581
631