mmif-python 0.5.0__tar.gz → 0.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.
- {mmif-python-0.5.0/mmif_python.egg-info → mmif-python-0.5.2}/PKG-INFO +2 -6
- mmif-python-0.5.2/README.md +3 -0
- mmif-python-0.5.2/VERSION +1 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/serialize/annotation.py +3 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/serialize/mmif.py +28 -3
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/serialize/model.py +14 -8
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/serialize/view.py +18 -2
- mmif-python-0.5.2/mmif/ver/__init__.py +2 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/vocabulary/__init__.py +2 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/vocabulary/base_types.py +36 -17
- {mmif-python-0.5.0 → mmif-python-0.5.2/mmif_python.egg-info}/PKG-INFO +2 -6
- {mmif-python-0.5.0 → mmif-python-0.5.2}/setup.py +3 -3
- mmif-python-0.5.0/README.md +0 -7
- mmif-python-0.5.0/VERSION +0 -1
- mmif-python-0.5.0/mmif/ver/__init__.py +0 -2
- {mmif-python-0.5.0 → mmif-python-0.5.2}/LICENSE +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/MANIFEST.in +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/__init__.py +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/res/__init__.py +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/res/clams.vocabulary.yaml +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/res/do-not-edit.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/res/mmif.json +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/serialize/__init__.py +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/ver/do-not-edit.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/vocabulary/annotation_types.py +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/vocabulary/do-not-edit.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif/vocabulary/document_types.py +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif_python.egg-info/SOURCES.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif_python.egg-info/dependency_links.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif_python.egg-info/requires.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/mmif_python.egg-info/top_level.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/requirements.txt +0 -0
- {mmif-python-0.5.0 → mmif-python-0.5.2}/setup.cfg +0 -0
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
License: UNKNOWN
|
|
9
|
-
Description:
|
|
10
|
-
|
|
11
|
-
**NOTE** that this project is in pre-alpha and being actively developed. Nothing is guaranteed to reliably work for the moment and developer need to be very careful when using APIs implemented here. Please use [the issue track](https://github.com/clamsproject/mmif/issues) to report bugs and malfunctions.
|
|
12
|
-
|
|
13
|
-
## MultiMedia Interchange Format
|
|
9
|
+
Description: ## MultiMedia Interchange Format
|
|
14
10
|
[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).
|
|
15
11
|
|
|
16
12
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.2
|
|
@@ -113,7 +113,10 @@ class Document(Annotation):
|
|
|
113
113
|
:param document_obj: the JSON data that defines the document
|
|
114
114
|
"""
|
|
115
115
|
def __init__(self, doc_obj: Optional[Union[bytes, str, dict]] = None) -> None:
|
|
116
|
+
# to store the parent view ID
|
|
116
117
|
self._parent_view_id = ''
|
|
118
|
+
self.reserved_names.add('_parent_view_id')
|
|
119
|
+
|
|
117
120
|
self._type: Union[ThingTypesBase, DocumentTypesBase] = ThingTypesBase('')
|
|
118
121
|
self.properties: DocumentProperties = DocumentProperties()
|
|
119
122
|
self.disallow_additional_properties()
|
|
@@ -68,6 +68,31 @@ class Mmif(MmifObject):
|
|
|
68
68
|
json_str = json.loads(json_str)
|
|
69
69
|
jsonschema.validators.validate(json_str, schema)
|
|
70
70
|
|
|
71
|
+
def serialize(self, pretty: bool = False, sanitize: bool = False) -> str:
|
|
72
|
+
if sanitize:
|
|
73
|
+
self.sanitize()
|
|
74
|
+
return super().serialize(pretty)
|
|
75
|
+
|
|
76
|
+
def sanitize(self):
|
|
77
|
+
"""
|
|
78
|
+
Sanitizes a Mmif object by running some safeguards.
|
|
79
|
+
Concretely, it performs the following before returning the JSON string.
|
|
80
|
+
|
|
81
|
+
#. validating output using built-in MMIF jsonschema
|
|
82
|
+
#. remove non-existing annotation types from ``contains`` metadata
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
for view in self.views:
|
|
86
|
+
existing_at_types = set(annotation.at_type for annotation in view.annotations)
|
|
87
|
+
to_pop = set()
|
|
88
|
+
for contains_at_type in view.metadata.contains.keys():
|
|
89
|
+
if contains_at_type not in existing_at_types:
|
|
90
|
+
to_pop.add(contains_at_type)
|
|
91
|
+
for key in to_pop:
|
|
92
|
+
view.metadata.contains.pop(key)
|
|
93
|
+
serialized = self.serialize()
|
|
94
|
+
self.validate(serialized)
|
|
95
|
+
|
|
71
96
|
def new_view_id(self) -> str:
|
|
72
97
|
"""
|
|
73
98
|
Fetches an ID for a new view.
|
|
@@ -241,14 +266,14 @@ class Mmif(MmifObject):
|
|
|
241
266
|
:return: a dict that keyed by view IDs (str) and has lists of alignment Annotation objects as values.
|
|
242
267
|
"""
|
|
243
268
|
v_and_a = {}
|
|
244
|
-
at_type1 = ThingTypesBase.from_str(
|
|
245
|
-
at_type2 = ThingTypesBase.from_str(at_type2) if isinstance(at_type2, str) else at_type2
|
|
269
|
+
at_type1, at_type2 = [ThingTypesBase.from_str(x) if isinstance(x, str) else x for x in (at_type1, at_type2)]
|
|
246
270
|
assert at_type1 != at_type2, f"Alignment must be between two different types, given only one: {at_type1}"
|
|
247
271
|
for alignment_view in self.get_all_views_contain(AnnotationTypes.Alignment):
|
|
248
272
|
alignments = []
|
|
249
273
|
contains_meta = alignment_view.metadata.contains[AnnotationTypes.Alignment]
|
|
250
274
|
if 'sourceType' in contains_meta and 'targetType' in contains_meta:
|
|
251
|
-
aligned_types =
|
|
275
|
+
aligned_types = [ThingTypesBase.from_str(x)
|
|
276
|
+
for x in {contains_meta['sourceType'], contains_meta['targetType']}]
|
|
252
277
|
if len(aligned_types) == 2 and at_type1 in aligned_types and at_type2 in aligned_types:
|
|
253
278
|
alignments.extend(alignment_view.annotations)
|
|
254
279
|
else:
|
|
@@ -37,7 +37,7 @@ class MmifObject(object):
|
|
|
37
37
|
an actual representation with a JSON formatted string or equivalent
|
|
38
38
|
`dict` object argument.
|
|
39
39
|
|
|
40
|
-
This superclass has
|
|
40
|
+
This superclass has four specially designed instance variables, and these
|
|
41
41
|
variable names cannot be used as attribute names for MMIF objects.
|
|
42
42
|
|
|
43
43
|
1. _unnamed_attributes:
|
|
@@ -54,12 +54,15 @@ class MmifObject(object):
|
|
|
54
54
|
When serialize, an object will skip its *empty* (e.g. zero-length, or None)
|
|
55
55
|
attributes unless they are in this list. Otherwise, the serialized JSON
|
|
56
56
|
string would have empty representations (e.g. ``""``, ``[]``).
|
|
57
|
+
4. _exclude_from_diff:
|
|
58
|
+
This is a simple list of names of attributes that should be excluded from
|
|
59
|
+
the diff calculation in ``__eq__``.
|
|
57
60
|
|
|
58
61
|
# TODO (krim @ 8/17/20): this dict is however, a duplicate with the type hints in the class definition.
|
|
59
62
|
Maybe there is a better way to utilize type hints (e.g. getting them as a programmatically), but for now
|
|
60
63
|
developers should be careful to add types to hints as well as to this dict.
|
|
61
64
|
|
|
62
|
-
Also note that those
|
|
65
|
+
Also note that those special attributes MUST be set in the __init__()
|
|
63
66
|
before calling super method, otherwise deserialization will not work.
|
|
64
67
|
|
|
65
68
|
And also, a subclass that has one or more *named* attributes, it must
|
|
@@ -74,17 +77,15 @@ class MmifObject(object):
|
|
|
74
77
|
an ID value automatically generated, based on its parent object.
|
|
75
78
|
"""
|
|
76
79
|
|
|
77
|
-
reserved_names: Set = {
|
|
80
|
+
reserved_names: Set[str] = {
|
|
78
81
|
'reserved_names',
|
|
79
82
|
'_unnamed_attributes',
|
|
80
83
|
'_attribute_classes',
|
|
81
84
|
'_required_attributes',
|
|
82
|
-
|
|
83
|
-
'_parent_view_id',
|
|
84
|
-
# used in View class to autogenerate annotation ids
|
|
85
|
-
'_id_counts'
|
|
85
|
+
'_exclude_from_diff'
|
|
86
86
|
}
|
|
87
87
|
_unnamed_attributes: Optional[dict]
|
|
88
|
+
_exclude_from_diff: Set[str]
|
|
88
89
|
_attribute_classes: Dict[str, Type] = {} # Mapping: str -> Type
|
|
89
90
|
|
|
90
91
|
def __init__(self, mmif_obj: Optional[Union[bytes, str, dict]] = None) -> None:
|
|
@@ -92,6 +93,8 @@ class MmifObject(object):
|
|
|
92
93
|
mmif_obj = mmif_obj.decode('utf8')
|
|
93
94
|
if not hasattr(self, '_required_attributes'):
|
|
94
95
|
self._required_attributes = []
|
|
96
|
+
if not hasattr(self, '_exclude_from_diff'):
|
|
97
|
+
self._exclude_from_diff = set()
|
|
95
98
|
if not hasattr(self, '_unnamed_attributes'):
|
|
96
99
|
self._unnamed_attributes = {}
|
|
97
100
|
if mmif_obj is not None:
|
|
@@ -253,7 +256,10 @@ class MmifObject(object):
|
|
|
253
256
|
|
|
254
257
|
def __eq__(self, other) -> bool:
|
|
255
258
|
return isinstance(other, type(self)) and \
|
|
256
|
-
len(DeepDiff(self, other, report_repetition=True, exclude_types=[datetime]
|
|
259
|
+
len(DeepDiff(self, other, report_repetition=True, exclude_types=[datetime],
|
|
260
|
+
# https://github.com/clamsproject/mmif-python/issues/214
|
|
261
|
+
exclude_paths=self._exclude_from_diff)
|
|
262
|
+
) == 0
|
|
257
263
|
|
|
258
264
|
def __len__(self) -> int:
|
|
259
265
|
"""
|
|
@@ -31,7 +31,11 @@ class View(MmifObject):
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
def __init__(self, view_obj: Optional[Union[bytes, str, dict]] = None) -> None:
|
|
34
|
+
# used to autogenerate annotation ids
|
|
34
35
|
self._id_counts = {}
|
|
36
|
+
self.reserved_names.add("_id_counts")
|
|
37
|
+
self._exclude_from_diff = {"_id_counts"}
|
|
38
|
+
|
|
35
39
|
self.id: str = ''
|
|
36
40
|
self.metadata: ViewMetadata = ViewMetadata()
|
|
37
41
|
self.annotations: AnnotationsList = AnnotationsList()
|
|
@@ -246,12 +250,21 @@ class ViewMetadata(MmifObject):
|
|
|
246
250
|
'error': ErrorDict,
|
|
247
251
|
'contains': ContainsDict
|
|
248
252
|
}
|
|
249
|
-
# in theory,
|
|
253
|
+
# in theory, *oneOf* `contains`, `error`, or `warnings` should appear in a `view`
|
|
250
254
|
# but with current implementation, there's no easy way to set a condition
|
|
251
255
|
# for `oneOf` requirement
|
|
252
256
|
# see MmifObject::_required_attributes in model.py
|
|
257
|
+
# also see this class' `_serialize()` override implementation
|
|
253
258
|
super().__init__(viewmetadata_obj)
|
|
254
259
|
|
|
260
|
+
def _serialize(self, alt_container: Optional[Dict] = None) -> dict:
|
|
261
|
+
serialized = super()._serialize()
|
|
262
|
+
# `_serialize()` eliminates any *empty* attributes, so
|
|
263
|
+
# when no "contains", "errors", nor "warnings", at least add an empty contains back
|
|
264
|
+
if not (self.contains.items() or self.error or self.warnings):
|
|
265
|
+
serialized['contains'] = {}
|
|
266
|
+
return serialized
|
|
267
|
+
|
|
255
268
|
def new_contain(self, at_type: Union[str, ThingTypesBase], **contains_metadata) -> Optional['Contain']:
|
|
256
269
|
"""
|
|
257
270
|
Adds a new element to the ``contains`` dictionary.
|
|
@@ -364,4 +377,7 @@ class ContainsDict(DataDict[ThingTypesBase, Contain]):
|
|
|
364
377
|
return self._items.get(key, default)
|
|
365
378
|
|
|
366
379
|
def __contains__(self, item: Union[str, ThingTypesBase]):
|
|
367
|
-
return item in
|
|
380
|
+
return item in self._items
|
|
381
|
+
|
|
382
|
+
def pop(self, key):
|
|
383
|
+
self._items.pop(key)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# This file is auto-generated by setup.py
|
|
2
|
+
import warnings
|
|
2
3
|
from typing import ClassVar
|
|
3
4
|
|
|
5
|
+
|
|
4
6
|
class TypesBase(object):
|
|
5
7
|
"""
|
|
6
8
|
Base class for arbitrary vocabulary type.
|
|
@@ -10,8 +12,10 @@ class TypesBase(object):
|
|
|
10
12
|
_prefixes = {}
|
|
11
13
|
base_uri: str
|
|
12
14
|
shortname: str
|
|
13
|
-
|
|
15
|
+
fuzzy_eq: bool
|
|
16
|
+
|
|
14
17
|
def __init__(self, type_uri: str):
|
|
18
|
+
self.fuzzy_eq = False
|
|
15
19
|
self.parse_names(type_uri)
|
|
16
20
|
if self.__repr__() not in self.__class__._prefixes:
|
|
17
21
|
# prepare auto-inferred prefix for Annotations.[].id field
|
|
@@ -109,6 +113,10 @@ class ClamsTypesBase(TypesBase):
|
|
|
109
113
|
version: str
|
|
110
114
|
dev_version: ClassVar[str] = 'develop'
|
|
111
115
|
|
|
116
|
+
def __init__(self, type_uri, fuzzymode=True):
|
|
117
|
+
super().__init__(type_uri)
|
|
118
|
+
self.fuzzy_eq = fuzzymode
|
|
119
|
+
|
|
112
120
|
def parse_names(self, type_uri:str):
|
|
113
121
|
if not type_uri:
|
|
114
122
|
self.base_uri, self.shortname, self.version = "", "", ""
|
|
@@ -130,27 +138,38 @@ class ClamsTypesBase(TypesBase):
|
|
|
130
138
|
|
|
131
139
|
def __hash__(self):
|
|
132
140
|
return hash(str(self))
|
|
141
|
+
|
|
142
|
+
def normalize_attype_vers(self):
|
|
143
|
+
if '.' in self.version and self._check_legacy_version(self.version):
|
|
144
|
+
# legacy mapping: see https://github.com/clamsproject/mmif/issues/14#issuecomment-1504439497
|
|
145
|
+
if self.version == '0.4.2' and self.shortname == 'Annotation':
|
|
146
|
+
return 'v2'
|
|
147
|
+
elif self.version.startswith('0.4.'):
|
|
148
|
+
return 'v1'
|
|
149
|
+
else:
|
|
150
|
+
return self.version
|
|
133
151
|
|
|
134
|
-
def
|
|
135
|
-
if isinstance(other, str):
|
|
136
|
-
other = ThingTypesBase.from_str(other)
|
|
152
|
+
def _eq_internal(self, other, fuzzy):
|
|
137
153
|
if isinstance(other, ClamsTypesBase):
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
and self.shortname == other.shortname \
|
|
150
|
-
and vers[0] == vers[1]
|
|
154
|
+
if fuzzy:
|
|
155
|
+
vers = [x.normalize_attype_vers() for x in (self, other)]
|
|
156
|
+
if self.base_uri == other.base_uri and self.shortname == other.shortname:
|
|
157
|
+
if vers[0] != vers[1]:
|
|
158
|
+
warnings.warn(f'version difference detected: {self.version}&{other.version}@{self.shortname}')
|
|
159
|
+
return True
|
|
160
|
+
return False
|
|
161
|
+
else:
|
|
162
|
+
return self.base_uri == other.base_uri \
|
|
163
|
+
and self.shortname == other.shortname \
|
|
164
|
+
and self.version == other.version
|
|
151
165
|
else:
|
|
152
166
|
return False
|
|
153
167
|
|
|
168
|
+
def __eq__(self, other):
|
|
169
|
+
if isinstance(other, str):
|
|
170
|
+
other = ThingTypesBase.from_str(other)
|
|
171
|
+
return self._eq_internal(other, self.fuzzy_eq and other.fuzzy_eq)
|
|
172
|
+
|
|
154
173
|
def __repr__(self):
|
|
155
174
|
if self.version[0] == 'v':
|
|
156
175
|
return f'{self.base_uri}/vocabulary/{self.shortname}/{self.version}'
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mmif-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
License: UNKNOWN
|
|
9
|
-
Description:
|
|
10
|
-
|
|
11
|
-
**NOTE** that this project is in pre-alpha and being actively developed. Nothing is guaranteed to reliably work for the moment and developer need to be very careful when using APIs implemented here. Please use [the issue track](https://github.com/clamsproject/mmif/issues) to report bugs and malfunctions.
|
|
12
|
-
|
|
13
|
-
## MultiMedia Interchange Format
|
|
9
|
+
Description: ## MultiMedia Interchange Format
|
|
14
10
|
[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).
|
|
15
11
|
|
|
16
12
|
|
|
@@ -134,7 +134,7 @@ def generate_vocabulary(spec_version, clams_types_vers):
|
|
|
134
134
|
f"from .{mod_name} import {class_name}"
|
|
135
135
|
for mod_name, classes in types.items()
|
|
136
136
|
for class_name in classes
|
|
137
|
-
)+'\n'
|
|
137
|
+
) + '\n\n' + "typevers = {**ThingType.typevers, **AnnotationTypes.typevers, **DocumentTypes.typevers}" + '\n'
|
|
138
138
|
)
|
|
139
139
|
document_types = []
|
|
140
140
|
annotation_types = []
|
|
@@ -161,7 +161,7 @@ def generate_vocabulary(spec_version, clams_types_vers):
|
|
|
161
161
|
return vocabulary_dir
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
def get_latest_mmif_gittag(
|
|
164
|
+
def get_latest_mmif_gittag():
|
|
165
165
|
# vmaj, vmin, vpat = version.split('.')[0:3]
|
|
166
166
|
res = request.urlopen('https://api.github.com/repos/clamsproject/mmif/git/refs/tags')
|
|
167
167
|
body = json.loads(res.read())
|
|
@@ -200,7 +200,7 @@ def prep_ext_files(setuptools_cmd):
|
|
|
200
200
|
def mod_run(self):
|
|
201
201
|
# will infer the `spec_ver` from the latest git tag available either on GH or local `mmif` repository.
|
|
202
202
|
# NOTE that when `make develop`, it will use specification files from upstream "develop" branch of `mmif` repo
|
|
203
|
-
latest_mmif_gittag = get_latest_mmif_gittag(
|
|
203
|
+
latest_mmif_gittag = get_latest_mmif_gittag()
|
|
204
204
|
spec_file_gitref = latest_mmif_gittag if '.dev' not in version else 'develop'
|
|
205
205
|
# legacy version tags were formatted as xx-a.b.c (e.g., vocab-0.0.1)
|
|
206
206
|
spec_version = latest_mmif_gittag.split('-')[-1]
|
mmif-python-0.5.0/README.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# MMIF for python
|
|
2
|
-
|
|
3
|
-
**NOTE** that this project is in pre-alpha and being actively developed. Nothing is guaranteed to reliably work for the moment and developer need to be very careful when using APIs implemented here. Please use [the issue track](https://github.com/clamsproject/mmif/issues) to report bugs and malfunctions.
|
|
4
|
-
|
|
5
|
-
## MultiMedia Interchange Format
|
|
6
|
-
[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).
|
|
7
|
-
|
mmif-python-0.5.0/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.5.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|