mmif-python 1.0.14__tar.gz → 1.0.15__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 (45) hide show
  1. mmif_python-1.0.15/PKG-INFO +46 -0
  2. mmif_python-1.0.15/VERSION +1 -0
  3. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/mmif.py +120 -112
  4. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/video_document_helper.py +40 -2
  5. mmif_python-1.0.15/mmif/ver/__init__.py +2 -0
  6. mmif_python-1.0.15/mmif_python.egg-info/PKG-INFO +46 -0
  7. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/SOURCES.txt +5 -1
  8. mmif_python-1.0.15/tests/test_serialize.py +1354 -0
  9. mmif_python-1.0.15/tests/test_utils.py +176 -0
  10. mmif_python-1.0.15/tests/test_versioncompat.py +56 -0
  11. mmif_python-1.0.15/tests/test_vocab.py +56 -0
  12. mmif-python-1.0.14/PKG-INFO +0 -37
  13. mmif-python-1.0.14/VERSION +0 -1
  14. mmif-python-1.0.14/mmif/ver/__init__.py +0 -2
  15. mmif-python-1.0.14/mmif_python.egg-info/PKG-INFO +0 -37
  16. {mmif-python-1.0.14 → mmif_python-1.0.15}/LICENSE +0 -0
  17. {mmif-python-1.0.14 → mmif_python-1.0.15}/MANIFEST.in +0 -0
  18. {mmif-python-1.0.14 → mmif_python-1.0.15}/README.md +0 -0
  19. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/__init__.py +0 -0
  20. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/__init__.py +0 -0
  21. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/clams.vocabulary.yaml +0 -0
  22. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/do-not-edit.txt +0 -0
  23. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/res/mmif.json +0 -0
  24. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/__init__.py +0 -0
  25. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/annotation.py +0 -0
  26. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/model.py +0 -0
  27. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/serialize/view.py +0 -0
  28. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/__init__.py +0 -0
  29. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/sequence_helper.py +0 -0
  30. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/utils/timeunit_helper.py +0 -0
  31. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/ver/do-not-edit.txt +0 -0
  32. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/__init__.py +0 -0
  33. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/annotation_types.py +0 -0
  34. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/base_types.py +0 -0
  35. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/do-not-edit.txt +0 -0
  36. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif/vocabulary/document_types.py +0 -0
  37. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_docloc_http/__init__.py +0 -0
  38. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/dependency_links.txt +0 -0
  39. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/requires.txt +0 -0
  40. {mmif-python-1.0.14 → mmif_python-1.0.15}/mmif_python.egg-info/top_level.txt +0 -0
  41. {mmif-python-1.0.14 → mmif_python-1.0.15}/requirements.cv +0 -0
  42. {mmif-python-1.0.14 → mmif_python-1.0.15}/requirements.seq +0 -0
  43. {mmif-python-1.0.14 → mmif_python-1.0.15}/requirements.txt +0 -0
  44. {mmif-python-1.0.14 → mmif_python-1.0.15}/setup.cfg +0 -0
  45. {mmif-python-1.0.14 → mmif_python-1.0.15}/setup.py +0 -0
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.1
2
+ Name: mmif-python
3
+ Version: 1.0.15
4
+ Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
5
+ Home-page: https://mmif.clams.ai
6
+ Author: Brandeis Lab for Linguistics and Computation
7
+ Author-email: admin@clams.ai
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: deepdiff>5
16
+ Requires-Dist: jsonschema
17
+ Provides-Extra: seq
18
+ Requires-Dist: numpy; extra == "seq"
19
+ Provides-Extra: cv
20
+ Requires-Dist: pillow; extra == "cv"
21
+ Requires-Dist: opencv-python; extra == "cv"
22
+ Requires-Dist: ffmpeg-python; extra == "cv"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: pytest-pep8; extra == "dev"
26
+ Requires-Dist: pytest-cov; extra == "dev"
27
+ Requires-Dist: pytype; extra == "dev"
28
+
29
+ ## MultiMedia Interchange Format
30
+ [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).
31
+
32
+
33
+ ## mmif-python
34
+ `mmif-python` is a Python implementation of the MMIF data format.
35
+ `mmif-python` provides various helper classes and functions to handle MMIF JSON in Python,
36
+ including ;
37
+
38
+ 1. de-/serialization of MMIF internal data structures to/from JSON
39
+ 2. validation of MMIF JSON
40
+ 3. handling of CLAMS vocabulary types
41
+ 4. navigation of MMIF object via various "search" methods (e.g. `mmif.get_all_views_contain(vocab_type))`)
42
+
43
+ ## For more ...
44
+ * [Version history and patch notes](https://github.com/clamsproject/mmif-python/blob/main/CHANGELOG.md)
45
+ * [MMIF Python API documentation](https://clamsproject.github.io/mmif-python)
46
+ * [MMIF JSON specification and schema](https://clamsproject.github.io/mmif)
@@ -0,0 +1 @@
1
+ 1.0.15
@@ -24,6 +24,104 @@ from .view import View
24
24
  __all__ = ['Mmif']
25
25
 
26
26
 
27
+ class MmifMetadata(MmifObject):
28
+ """
29
+ Basic MmifObject class to contain the top-level metadata of a MMIF file.
30
+
31
+ :param metadata_obj: the JSON data
32
+ """
33
+
34
+ def __init__(self, metadata_obj: Optional[Union[bytes, str, dict]] = None) -> None:
35
+ # TODO (krim @ 10/7/20): there could be a better name and a better way to give a value to this
36
+ self.mmif: str = f"http://mmif.clams.ai/{mmif.__specver__}"
37
+ self._required_attributes = ["mmif"]
38
+ super().__init__(metadata_obj)
39
+
40
+
41
+ class DocumentsList(DataList[Document]):
42
+ """
43
+ DocumentsList object that implements :class:`mmif.serialize.model.DataList`
44
+ for :class:`mmif.serialize.document.Document`.
45
+ """
46
+ _items: Dict[str, Document]
47
+
48
+ def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
49
+ """
50
+ Extends base ``_deserialize`` method to initialize ``items`` as a dict from
51
+ document IDs to :class:`mmif.serialize.document.Document` objects.
52
+
53
+ :param input_list: the JSON data that defines the list of documents
54
+ :return: None
55
+ """
56
+ self._items = {item['properties']['id']: Document(item) for item in input_list}
57
+
58
+ def append(self, value: Document, overwrite=False) -> None:
59
+ """
60
+ Appends a document to the list.
61
+
62
+ Fails if there is already a document with the same ID
63
+ in the list, unless ``overwrite`` is set to True.
64
+
65
+ :param value: the :class:`mmif.serialize.document.Document`
66
+ object to add
67
+ :param overwrite: if set to True, will overwrite an
68
+ existing document with the same ID
69
+ :raises KeyError: if ``overwrite`` is set to False and
70
+ a document with the same ID exists
71
+ in the list
72
+ :return: None
73
+ """
74
+ super()._append_with_key(value.id, value, overwrite)
75
+
76
+
77
+ class ViewsList(DataList[View]):
78
+ """
79
+ ViewsList object that implements :class:`mmif.serialize.model.DataList`
80
+ for :class:`mmif.serialize.view.View`.
81
+ """
82
+ _items: Dict[str, View]
83
+
84
+ def __init__(self, mmif_obj: Optional[Union[bytes, str, list]] = None):
85
+ super().__init__(mmif_obj)
86
+
87
+ def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
88
+ """
89
+ Extends base ``_deserialize`` method to initialize ``items`` as a dict from
90
+ view IDs to :class:`mmif.serialize.view.View` objects.
91
+
92
+ :param input_list: the JSON data that defines the list of views
93
+ :return: None
94
+ """
95
+ if input_list:
96
+ self._items = {item['id']: View(item) for item in input_list}
97
+
98
+ def append(self, value: View, overwrite=False) -> None:
99
+ """
100
+ Appends a view to the list.
101
+
102
+ Fails if there is already a view with the same ID
103
+ in the list, unless ``overwrite`` is set to True.
104
+
105
+ :param value: the :class:`mmif.serialize.view.View`
106
+ object to add
107
+ :param overwrite: if set to True, will overwrite an
108
+ existing view with the same ID
109
+ :raises KeyError: if ``overwrite`` is set to False and
110
+ a view with the same ID exists
111
+ in the list
112
+ :return: None
113
+ """
114
+ super()._append_with_key(value.id, value, overwrite)
115
+
116
+ def get_last(self) -> Optional[View]:
117
+ """
118
+ Returns the last view appended to the list.
119
+ """
120
+ for view in reversed(self._items.values()):
121
+ if 'error' not in view.metadata and 'warning' not in view.metadata:
122
+ return view
123
+
124
+
27
125
  class Mmif(MmifObject):
28
126
  """
29
127
  MmifObject that represents a full MMIF file.
@@ -560,131 +658,41 @@ class Mmif(MmifObject):
560
658
  """
561
659
  return self._get_linear_anchor_point(annotation, start=False)
562
660
 
563
- # pytype: disable=bad-return-type
564
- def __getitem__(self, item: str) -> Union[Document, View, Annotation]:
661
+ def __getitem__(self, item: str) \
662
+ -> Union[Document, View, Annotation, MmifMetadata, DocumentsList, ViewsList]:
565
663
  """
566
- getitem implementation for Mmif. When nothing is found, this will raise an error
567
- rather than returning a None (although pytype doesn't think so...)
664
+ getitem implementation for Mmif. This will try to find any object, given an identifier or an immediate
665
+ attribute name. When nothing is found, this will raise an error rather than returning a None
568
666
 
569
667
  :raises KeyError: if the item is not found or if the search results are ambiguous
570
- :param item: the search string, a document ID, a view ID, or a view-scoped annotation ID
668
+ :param item: an attribute name or an object identifier (a document ID, a view ID, or an annotation ID). When
669
+ annotation ID is given as a "short" ID (without view ID prefix), the method will try to find a
670
+ match from the first view, and return immediately if found.
571
671
  :return: the object searched for
672
+ :raise KeyError: if the item is not found or multiple objects are found with the same ID
572
673
  """
573
674
  if item in self._named_attributes():
574
675
  return self.__dict__[item]
575
676
  split_attempt = item.split(self.id_delimiter)
576
677
 
577
- document_result = self.documents.get(split_attempt[0])
578
- view_result = self.views.get(split_attempt[0])
678
+ found = []
579
679
 
580
680
  if len(split_attempt) == 1:
581
- anno_result = None
582
- elif view_result:
583
- anno_result = view_result[split_attempt[1]]
681
+ found.append(self.documents.get(split_attempt[0]))
682
+ found.append(self.views.get(split_attempt[0]))
683
+ for view in self.views:
684
+ found.append(view.annotations.get(split_attempt[0]))
685
+ elif len(split_attempt) == 2:
686
+ v = self.get_view_by_id(split_attempt[0])
687
+ if v is not None:
688
+ found.append(v.annotations.get(split_attempt[1]))
584
689
  else:
585
690
  raise KeyError("Tried to subscript into a view that doesn't exist")
691
+ found = [x for x in found if x is not None]
586
692
 
587
- if view_result and document_result:
693
+ if len(found) > 1:
588
694
  raise KeyError("Ambiguous ID search result")
589
- if not (view_result or document_result):
695
+ elif len(found) == 0:
590
696
  raise KeyError("ID not found: %s" % item)
591
- return anno_result or view_result or document_result
592
- # pytype: enable=bad-return-type
593
-
594
-
595
- class MmifMetadata(MmifObject):
596
- """
597
- Basic MmifObject class to contain the top-level metadata of a MMIF file.
598
-
599
- :param metadata_obj: the JSON data
600
- """
601
-
602
- def __init__(self, metadata_obj: Optional[Union[bytes, str, dict]] = None) -> None:
603
- # TODO (krim @ 10/7/20): there could be a better name and a better way to give a value to this
604
- self.mmif: str = f"http://mmif.clams.ai/{mmif.__specver__}"
605
- self._required_attributes = ["mmif"]
606
- super().__init__(metadata_obj)
607
-
608
-
609
- class DocumentsList(DataList[Document]):
610
- """
611
- DocumentsList object that implements :class:`mmif.serialize.model.DataList`
612
- for :class:`mmif.serialize.document.Document`.
613
- """
614
- _items: Dict[str, Document]
615
-
616
- def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
617
- """
618
- Extends base ``_deserialize`` method to initialize ``items`` as a dict from
619
- document IDs to :class:`mmif.serialize.document.Document` objects.
620
-
621
- :param input_list: the JSON data that defines the list of documents
622
- :return: None
623
- """
624
- self._items = {item['properties']['id']: Document(item) for item in input_list}
625
-
626
- def append(self, value: Document, overwrite=False) -> None:
627
- """
628
- Appends a document to the list.
629
-
630
- Fails if there is already a document with the same ID
631
- in the list, unless ``overwrite`` is set to True.
632
-
633
- :param value: the :class:`mmif.serialize.document.Document`
634
- object to add
635
- :param overwrite: if set to True, will overwrite an
636
- existing document with the same ID
637
- :raises KeyError: if ``overwrite`` is set to False and
638
- a document with the same ID exists
639
- in the list
640
- :return: None
641
- """
642
- super()._append_with_key(value.id, value, overwrite)
643
-
644
-
645
- class ViewsList(DataList[View]):
646
- """
647
- ViewsList object that implements :class:`mmif.serialize.model.DataList`
648
- for :class:`mmif.serialize.view.View`.
649
- """
650
- _items: Dict[str, View]
651
-
652
- def __init__(self, mmif_obj: Optional[Union[bytes, str, list]] = None):
653
- super().__init__(mmif_obj)
654
-
655
- def _deserialize(self, input_list: list) -> None: # pytype: disable=signature-mismatch
656
- """
657
- Extends base ``_deserialize`` method to initialize ``items`` as a dict from
658
- view IDs to :class:`mmif.serialize.view.View` objects.
659
-
660
- :param input_list: the JSON data that defines the list of views
661
- :return: None
662
- """
663
- if input_list:
664
- self._items = {item['id']: View(item) for item in input_list}
665
-
666
- def append(self, value: View, overwrite=False) -> None:
667
- """
668
- Appends a view to the list.
669
-
670
- Fails if there is already a view with the same ID
671
- in the list, unless ``overwrite`` is set to True.
672
-
673
- :param value: the :class:`mmif.serialize.view.View`
674
- object to add
675
- :param overwrite: if set to True, will overwrite an
676
- existing view with the same ID
677
- :raises KeyError: if ``overwrite`` is set to False and
678
- a view with the same ID exists
679
- in the list
680
- :return: None
681
- """
682
- super()._append_with_key(value.id, value, overwrite)
683
-
684
- def get_last(self) -> Optional[View]:
685
- """
686
- Returns the last view appended to the list.
687
- """
688
- for view in reversed(self._items.values()):
689
- if 'error' not in view.metadata and 'warning' not in view.metadata:
690
- return view
697
+ else:
698
+ return found[-1]
@@ -6,7 +6,7 @@ import math
6
6
  import mmif
7
7
  from mmif import Annotation, Document, Mmif
8
8
  from mmif.utils.timeunit_helper import convert
9
- from mmif.vocabulary import DocumentTypes
9
+ from mmif.vocabulary import DocumentTypes, AnnotationTypes
10
10
 
11
11
  for cv_dep in ('cv2', 'ffmpeg', 'PIL'):
12
12
  try:
@@ -83,14 +83,16 @@ def extract_frames_as_images(video_document: Document, framenums: List[int], as_
83
83
  frames = []
84
84
  video = capture(video_document)
85
85
  cur_f = 0
86
+ tot_fcount = video_document.get_property(FRAMECOUNT_DOCPROP_KEY)
86
87
  while True:
87
- if not framenums or cur_f > video_document.get_property(FRAMECOUNT_DOCPROP_KEY):
88
+ if not framenums or cur_f > tot_fcount:
88
89
  break
89
90
  ret, frame = video.read()
90
91
  if cur_f == framenums[0]:
91
92
  if not ret:
92
93
  sec = convert(cur_f, 'f', 's', video_document.get_property(FPS_DOCPROP_KEY))
93
94
  warnings.warn(f'Frame #{cur_f} ({sec}s) could not be read from the video {video_document.id}.')
95
+ cur_f += 1
94
96
  continue
95
97
  frames.append(Image.fromarray(frame[:, :, ::-1]) if as_PIL else frame)
96
98
  framenums.pop(0)
@@ -125,6 +127,42 @@ def extract_mid_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
125
127
  return extract_frames_as_images(vd, [get_mid_framenum(mmif, time_frame)], as_PIL=as_PIL)[0]
126
128
 
127
129
 
130
+ def get_representative_framenum(mmif: Mmif, time_frame: Annotation):
131
+ """
132
+ Calculates the representative frame number from an annotation.
133
+
134
+ :param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
135
+ :param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation containing a `representatives` property (``"@type": ".../TimeFrame/..."``)
136
+ :return: representative frame number as an integer
137
+ """
138
+ if 'representatives' not in time_frame.properties:
139
+ raise ValueError(f'The time frame {time_frame.id} does not have a representative.')
140
+ timeunit = time_frame.get_property('timeUnit')
141
+ video_document = mmif[time_frame.get_property('document')]
142
+ fps = get_framerate(video_document)
143
+ representatives = time_frame.get_property('representatives')
144
+ top_representative_id = representatives[0]
145
+ try:
146
+ representative_timepoint_anno = mmif[time_frame._parent_view_id+time_frame.id_delimiter+top_representative_id]
147
+ except KeyError:
148
+ raise ValueError(f'Representative timepoint {top_representative_id} not found in any view.')
149
+ return convert(representative_timepoint_anno.get_property('timePoint'), timeunit, 'frame', fps)
150
+
151
+
152
+ def extract_representative_frame(mmif: Mmif, time_frame: Annotation, as_PIL: bool = False):
153
+ """
154
+ Extracts the representative frame of an annotation as a numpy ndarray or PIL Image.
155
+
156
+ :param mmif: :py:class:`~mmif.serialize.mmif.Mmif` instance
157
+ :param time_frame: :py:class:`~mmif.serialize.annotation.Annotation` instance that holds a time interval annotation (``"@type": ".../TimeFrame/..."``)
158
+ :param as_PIL: return :py:class:`~PIL.Image.Image` instead of :py:class:`~numpy.ndarray`
159
+ :return: frame as a :py:class:`numpy.ndarray` or :py:class:`PIL.Image.Image`
160
+ """
161
+ video_document = mmif[time_frame.get_property('document')]
162
+ rep_frame_num = get_representative_framenum(mmif, time_frame)
163
+ return extract_frames_as_images(video_document, [rep_frame_num], as_PIL=as_PIL)[0]
164
+
165
+
128
166
  def sample_frames(start_frame: int, end_frame: int, sample_rate: float = 1) -> List[int]:
129
167
  """
130
168
  Helper function to sample frames from a time interval.
@@ -0,0 +1,2 @@
1
+ __version__ = "1.0.15"
2
+ __specver__ = "1.0.4"
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.1
2
+ Name: mmif-python
3
+ Version: 1.0.15
4
+ Summary: Python implementation of MultiMedia Interchange Format specification. (https://mmif.clams.ai)
5
+ Home-page: https://mmif.clams.ai
6
+ Author: Brandeis Lab for Linguistics and Computation
7
+ Author-email: admin@clams.ai
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: deepdiff>5
16
+ Requires-Dist: jsonschema
17
+ Provides-Extra: seq
18
+ Requires-Dist: numpy; extra == "seq"
19
+ Provides-Extra: cv
20
+ Requires-Dist: pillow; extra == "cv"
21
+ Requires-Dist: opencv-python; extra == "cv"
22
+ Requires-Dist: ffmpeg-python; extra == "cv"
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Requires-Dist: pytest-pep8; extra == "dev"
26
+ Requires-Dist: pytest-cov; extra == "dev"
27
+ Requires-Dist: pytype; extra == "dev"
28
+
29
+ ## MultiMedia Interchange Format
30
+ [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).
31
+
32
+
33
+ ## mmif-python
34
+ `mmif-python` is a Python implementation of the MMIF data format.
35
+ `mmif-python` provides various helper classes and functions to handle MMIF JSON in Python,
36
+ including ;
37
+
38
+ 1. de-/serialization of MMIF internal data structures to/from JSON
39
+ 2. validation of MMIF JSON
40
+ 3. handling of CLAMS vocabulary types
41
+ 4. navigation of MMIF object via various "search" methods (e.g. `mmif.get_all_views_contain(vocab_type))`)
42
+
43
+ ## For more ...
44
+ * [Version history and patch notes](https://github.com/clamsproject/mmif-python/blob/main/CHANGELOG.md)
45
+ * [MMIF Python API documentation](https://clamsproject.github.io/mmif-python)
46
+ * [MMIF JSON specification and schema](https://clamsproject.github.io/mmif)
@@ -32,4 +32,8 @@ mmif_python.egg-info/PKG-INFO
32
32
  mmif_python.egg-info/SOURCES.txt
33
33
  mmif_python.egg-info/dependency_links.txt
34
34
  mmif_python.egg-info/requires.txt
35
- mmif_python.egg-info/top_level.txt
35
+ mmif_python.egg-info/top_level.txt
36
+ tests/test_serialize.py
37
+ tests/test_utils.py
38
+ tests/test_versioncompat.py
39
+ tests/test_vocab.py