clams-python 1.1.6__tar.gz → 1.2.1__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.
- {clams-python-1.1.6/clams_python.egg-info → clams_python-1.2.1}/PKG-INFO +1 -1
- clams_python-1.2.1/VERSION +1 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/app/__init__.py +8 -11
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/app.py.template +1 -2
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/mmif_utils/source.py +32 -75
- clams_python-1.2.1/clams/ver/__init__.py +1 -0
- {clams-python-1.1.6 → clams_python-1.2.1/clams_python.egg-info}/PKG-INFO +1 -1
- {clams-python-1.1.6 → clams_python-1.2.1}/tests/test_clamsapp.py +20 -1
- {clams-python-1.1.6 → clams_python-1.2.1}/tests/test_clamscli.py +5 -0
- clams-python-1.1.6/VERSION +0 -1
- clams-python-1.1.6/clams/ver/__init__.py +0 -1
- {clams-python-1.1.6 → clams_python-1.2.1}/LICENSE +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/MANIFEST.in +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/README.md +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/__init__.py +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/appmetadata/__init__.py +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/__init__.py +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/.dockerignore.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/.gitignore.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/Containerfile.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/LICENSE.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/README.md.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/metadata.py.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/requirements.txt.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/mmif_utils/__init__.py +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/mmif_utils/rewind.py +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/restify/__init__.py +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams/serve/__init__.py +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/SOURCES.txt +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/dependency_links.txt +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/entry_points.txt +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/requires.txt +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/top_level.txt +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/requirements.txt +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/setup.cfg +0 -0
- {clams-python-1.1.6 → clams_python-1.2.1}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.2.1
|
|
@@ -122,7 +122,9 @@ class ClamsApp(ABC):
|
|
|
122
122
|
if key not in self.annotate_param_spec:
|
|
123
123
|
issued_warnings.append(UserWarning(f'An undefined parameter "{key}" (value: "{runtime_params[key]}") is passed'))
|
|
124
124
|
# this will do casting + refinement altogether
|
|
125
|
+
self.logger.debug(f"User parameters: {runtime_params}")
|
|
125
126
|
refined = self._refine_params(**runtime_params)
|
|
127
|
+
self.logger.debug(f"Refined parameters: {refined}")
|
|
126
128
|
pretty = refined.get('pretty', False)
|
|
127
129
|
with warnings.catch_warnings(record=True) as ws:
|
|
128
130
|
annotated = self._annotate(mmif, **refined)
|
|
@@ -190,7 +192,7 @@ class ClamsApp(ABC):
|
|
|
190
192
|
DeprecationWarning, stacklevel=2)
|
|
191
193
|
return self._refine_params(**runtime_params)
|
|
192
194
|
|
|
193
|
-
def sign_view(self, view: View, runtime_conf:
|
|
195
|
+
def sign_view(self, view: View, runtime_conf: dict) -> None:
|
|
194
196
|
"""
|
|
195
197
|
A method to "sign" a new view that this app creates at the beginning of annotation.
|
|
196
198
|
Signing will populate the view metadata with information and configuration of this app.
|
|
@@ -201,27 +203,22 @@ class ClamsApp(ABC):
|
|
|
201
203
|
:param view: a view to sign
|
|
202
204
|
:param runtime_conf: runtime configuration of the app as k-v pairs
|
|
203
205
|
"""
|
|
204
|
-
# TODO (krim @ 8/2/23): once all devs understood this change, make runtime_conf a required argument
|
|
205
|
-
if runtime_conf is None:
|
|
206
|
-
warnings.warn("`runtime_conf` argument for ClamsApp.sign_view() will "
|
|
207
|
-
"no longer be optional in the future. Please pass `runtime_params` "
|
|
208
|
-
"from _annotate() method.",
|
|
209
|
-
FutureWarning, stacklevel=2)
|
|
210
|
-
return
|
|
211
206
|
view.metadata.app = self.metadata.identifier
|
|
207
|
+
params_map = {p.name: p for p in self.metadata.parameters}
|
|
212
208
|
if self._RAW_PARAMS_KEY in runtime_conf:
|
|
213
209
|
for k, v in runtime_conf.items():
|
|
214
210
|
if k == self._RAW_PARAMS_KEY:
|
|
215
211
|
for orik, oriv in v.items():
|
|
216
|
-
if orik in
|
|
212
|
+
if orik in params_map and params_map[orik].multivalued:
|
|
217
213
|
view.metadata.add_parameter(orik, str(oriv))
|
|
218
214
|
else:
|
|
219
215
|
view.metadata.add_parameter(orik, oriv[0])
|
|
220
|
-
|
|
216
|
+
else:
|
|
217
|
+
view.metadata.add_app_configuration(k, v)
|
|
221
218
|
else:
|
|
222
219
|
# meaning the parameters directly from flask or argparser and values are in lists
|
|
223
220
|
for k, v in runtime_conf.items():
|
|
224
|
-
if k in
|
|
221
|
+
if k in params_map and params_map[k].multivalued:
|
|
225
222
|
view.metadata.add_parameter(k, str(v))
|
|
226
223
|
else:
|
|
227
224
|
view.metadata.add_parameter(k, v[0])
|
|
@@ -13,7 +13,6 @@ The app.py script does several things:
|
|
|
13
13
|
|
|
14
14
|
import argparse
|
|
15
15
|
import logging
|
|
16
|
-
from typing import Union
|
|
17
16
|
|
|
18
17
|
# Imports needed for Clams and MMIF.
|
|
19
18
|
# Non-NLP Clams applications will require AnnotationTypes
|
|
@@ -36,7 +35,7 @@ class $APP_CLASS_NAME(ClamsApp):
|
|
|
36
35
|
# When using the ``metadata.py`` leave this do-nothing "pass" method here.
|
|
37
36
|
pass
|
|
38
37
|
|
|
39
|
-
def _annotate(self, mmif:
|
|
38
|
+
def _annotate(self, mmif: Mmif, **parameters) -> Mmif:
|
|
40
39
|
# see https://sdk.clams.ai/autodoc/clams.app.html#clams.app.ClamsApp._annotate
|
|
41
40
|
raise NotImplementedError
|
|
42
41
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import itertools
|
|
3
3
|
import json
|
|
4
|
+
import pathlib
|
|
4
5
|
import sys
|
|
5
6
|
import textwrap
|
|
6
|
-
from os import path
|
|
7
7
|
from typing import Union, Generator, List, Optional, Iterable
|
|
8
8
|
from urllib.parse import urlparse
|
|
9
9
|
|
|
@@ -171,24 +171,18 @@ class WorkflowSource:
|
|
|
171
171
|
yield self.produce()
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
def generate_source_mmif_from_file(documents, prefix=None, **ignored):
|
|
175
|
-
from string import Template
|
|
174
|
+
def generate_source_mmif_from_file(documents, prefix=None, scheme='file', **ignored):
|
|
176
175
|
at_types = {
|
|
177
176
|
'video': DocumentTypes.VideoDocument,
|
|
178
177
|
'audio': DocumentTypes.AudioDocument,
|
|
179
178
|
'text': DocumentTypes.TextDocument,
|
|
180
179
|
'image': DocumentTypes.ImageDocument
|
|
181
180
|
}
|
|
182
|
-
template = Template('''{
|
|
183
|
-
"@type": "${at_type}",
|
|
184
|
-
"properties": {
|
|
185
|
-
"id": "${aid}",
|
|
186
|
-
"mime": "${mime}",
|
|
187
|
-
"location": "${location}" }
|
|
188
|
-
}''')
|
|
189
181
|
pl = WorkflowSource()
|
|
190
|
-
if prefix
|
|
191
|
-
|
|
182
|
+
if prefix:
|
|
183
|
+
prefix = pathlib.PurePosixPath(prefix)
|
|
184
|
+
if not prefix.is_absolute():
|
|
185
|
+
raise ValueError(f"prefix must be an absolute path; given \"{prefix}\".")
|
|
192
186
|
for doc_id, arg in enumerate(documents, start=1):
|
|
193
187
|
arg = arg.strip()
|
|
194
188
|
if len(arg) < 1:
|
|
@@ -200,57 +194,20 @@ def generate_source_mmif_from_file(documents, prefix=None, **ignored):
|
|
|
200
194
|
raise ValueError(
|
|
201
195
|
f'Invalid MIME types, or no MIME type and/or path provided, in argument {doc_id-1} to source'
|
|
202
196
|
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def generate_source_mmif_from_customscheme(documents, scheme, **ignored):
|
|
220
|
-
from string import Template
|
|
221
|
-
at_types = {
|
|
222
|
-
'video': DocumentTypes.VideoDocument,
|
|
223
|
-
'audio': DocumentTypes.AudioDocument,
|
|
224
|
-
'text': DocumentTypes.TextDocument,
|
|
225
|
-
'image': DocumentTypes.ImageDocument
|
|
226
|
-
}
|
|
227
|
-
template = Template('''{
|
|
228
|
-
"@type": "${at_type}",
|
|
229
|
-
"properties": {
|
|
230
|
-
"id": "${aid}",
|
|
231
|
-
"mime": "${mime}",
|
|
232
|
-
"location": "${location}" }
|
|
233
|
-
}''')
|
|
234
|
-
pl = WorkflowSource()
|
|
235
|
-
for doc_id, arg in enumerate(documents, start=1):
|
|
236
|
-
arg = arg.strip()
|
|
237
|
-
if len(arg) < 1:
|
|
238
|
-
continue
|
|
239
|
-
result = arg.split(':', maxsplit=1)
|
|
240
|
-
if len(result) == 2 and result[0].split('/', maxsplit=1)[0] in at_types:
|
|
241
|
-
mime, location = result
|
|
242
|
-
else:
|
|
243
|
-
raise ValueError(
|
|
244
|
-
f'Invalid MIME types, or no MIME type and/or path provided, in argument {doc_id-1} to source'
|
|
245
|
-
)
|
|
246
|
-
if urlparse(location).scheme == '':
|
|
247
|
-
location = scheme + '://' + location
|
|
248
|
-
doc = template.substitute(
|
|
249
|
-
at_type=str(at_types[mime.split('/', maxsplit=1)[0]]),
|
|
250
|
-
aid=f'd{doc_id}',
|
|
251
|
-
mime=mime,
|
|
252
|
-
location=location
|
|
253
|
-
)
|
|
197
|
+
location_uri = urlparse(location, scheme=scheme)
|
|
198
|
+
if location_uri.scheme == 'file':
|
|
199
|
+
location = pathlib.PurePosixPath(location_uri.path)
|
|
200
|
+
if prefix and location.is_absolute():
|
|
201
|
+
raise ValueError(f"when prefix is used, file location must not be an absolute path; given \"{location}\".")
|
|
202
|
+
elif not prefix and not location.is_absolute():
|
|
203
|
+
raise ValueError(f'file location must be an absolute path, or --prefix must be used; given \"{location}\".')
|
|
204
|
+
elif prefix and not location.is_absolute():
|
|
205
|
+
location = prefix / location
|
|
206
|
+
doc = Document()
|
|
207
|
+
doc.at_type = at_types[mime.split('/', maxsplit=1)[0]]
|
|
208
|
+
doc.properties.location = f"{location_uri.scheme}://{location}"
|
|
209
|
+
doc.properties.id = f'd{doc_id}'
|
|
210
|
+
doc.properties.mime = mime
|
|
254
211
|
pl.add_document(doc)
|
|
255
212
|
return pl.produce().serialize(pretty=True)
|
|
256
213
|
|
|
@@ -274,19 +231,22 @@ def prep_argparser(**kwargs):
|
|
|
274
231
|
default=None,
|
|
275
232
|
action='store',
|
|
276
233
|
nargs='+',
|
|
277
|
-
help=
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
234
|
+
help='This list of documents MUST be colon-delimited pairs of document types and file locations. A document '
|
|
235
|
+
'type can be one of `audio`, `video`, `text`, `image`, or a MIME type string (such as video/mp4). The '
|
|
236
|
+
'file locations MUST be valid URI strings (e.g. `file:///path/to/file.mp4`, or URI scheme part can be '
|
|
237
|
+
'omitted, when `--scheme` flag is used). Note that when `file://` scheme is used (default), locations '
|
|
238
|
+
'MUST BE POSIX forms (Windows forms are not supported). The output will be a MMIF file containing a '
|
|
239
|
+
'document for each of those file paths, with the appropriate ``@type`` and MIME type (if given).'
|
|
281
240
|
)
|
|
282
241
|
parser.add_argument(
|
|
283
242
|
'-p', '--prefix',
|
|
284
243
|
default=None,
|
|
285
244
|
metavar='PATH',
|
|
286
245
|
nargs='?',
|
|
287
|
-
help='An absolute path to use as prefix for file paths (ONLY WORKS with `file
|
|
288
|
-
'
|
|
289
|
-
'
|
|
246
|
+
help='An absolute path to use as prefix for file paths (ONLY WORKS with the default `file://` scheme, ignored '
|
|
247
|
+
'otherwise. MUST BE a POSIX form, Windows form is not supported). If prefix is set, document file paths '
|
|
248
|
+
'MUST be relative. Useful when creating source MMIF files from a system that\'s different from the '
|
|
249
|
+
'environment that actually runs the workflow (e.g. in a container).'
|
|
290
250
|
)
|
|
291
251
|
parser.add_argument(
|
|
292
252
|
'-o', '--output',
|
|
@@ -300,7 +260,7 @@ def prep_argparser(**kwargs):
|
|
|
300
260
|
default='file',
|
|
301
261
|
action='store',
|
|
302
262
|
nargs='?',
|
|
303
|
-
help='A scheme to associate with the document location URI. When not given, the default scheme is `file
|
|
263
|
+
help='A scheme to associate with the document location URI. When not given, the default scheme is `file://`.'
|
|
304
264
|
)
|
|
305
265
|
return parser
|
|
306
266
|
|
|
@@ -310,10 +270,7 @@ def main(args):
|
|
|
310
270
|
out_f = open(args.output, 'w')
|
|
311
271
|
else:
|
|
312
272
|
out_f = sys.stdout
|
|
313
|
-
|
|
314
|
-
mmif = generate_source_mmif_from_file(**vars(args))
|
|
315
|
-
else:
|
|
316
|
-
mmif = generate_source_mmif_from_customscheme(**vars(args))
|
|
273
|
+
mmif = generate_source_mmif_from_file(windows_path=False, **vars(args))
|
|
317
274
|
out_f.write(mmif)
|
|
318
275
|
return mmif
|
|
319
276
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.1"
|
|
@@ -228,7 +228,26 @@ class TestClamsApp(unittest.TestCase):
|
|
|
228
228
|
in_mmif.metadata.mmif = f"http://mmif.clams.ai/{incompat_ver}"
|
|
229
229
|
with pytest.raises(ValueError):
|
|
230
230
|
self.app.annotate(in_mmif)
|
|
231
|
-
|
|
231
|
+
|
|
232
|
+
def test_sign_view(self):
|
|
233
|
+
m = Mmif(self.in_mmif)
|
|
234
|
+
v1 = m.new_view()
|
|
235
|
+
self.app.sign_view(v1, {})
|
|
236
|
+
self.assertEqual(v1.metadata.app, self.app.metadata.identifier)
|
|
237
|
+
self.assertEqual(len(v1.metadata.parameters), 0)
|
|
238
|
+
v2 = m.new_view()
|
|
239
|
+
raw_query_string_arguments = {'undefined_param1': ['value1']} # values are lists as our restifier uses `to_dict(flat=False)`
|
|
240
|
+
self.app.sign_view(v2, self.app._refine_params(**raw_query_string_arguments))
|
|
241
|
+
self.assertEqual(v2.metadata.app, self.app.metadata.identifier)
|
|
242
|
+
self.assertEqual(len(v2.metadata.parameters), 1)
|
|
243
|
+
self.assertFalse(clams.ClamsApp._RAW_PARAMS_KEY in v2.metadata.appConfiguration)
|
|
244
|
+
for param in self.app.metadata.parameters:
|
|
245
|
+
# any parameter with defaults should be recorded
|
|
246
|
+
if param.default is not None:
|
|
247
|
+
self.assertTrue(param.name in v2.metadata.appConfiguration)
|
|
248
|
+
elif param.name not in raw_query_string_arguments:
|
|
249
|
+
self.assertFalse(param.name in v2.metadata.appConfiguration)
|
|
250
|
+
|
|
232
251
|
def test_annotate(self):
|
|
233
252
|
# The example app is hard-coded to **always** emit version mismatch warning
|
|
234
253
|
out_mmif = self.app.annotate(self.in_mmif)
|
|
@@ -3,6 +3,7 @@ import copy
|
|
|
3
3
|
import io
|
|
4
4
|
import os
|
|
5
5
|
import unittest
|
|
6
|
+
import unittest.mock
|
|
6
7
|
|
|
7
8
|
from mmif.serialize import Mmif
|
|
8
9
|
from mmif.vocabulary import DocumentTypes, AnnotationTypes
|
|
@@ -63,6 +64,10 @@ class TestSource(unittest.TestCase):
|
|
|
63
64
|
with self.assertRaises(ValueError):
|
|
64
65
|
self.generate_source_mmif()
|
|
65
66
|
|
|
67
|
+
@unittest.mock.patch('os.name', 'nt')
|
|
68
|
+
def test_on_windows(self):
|
|
69
|
+
self.test_accept_file_paths()
|
|
70
|
+
|
|
66
71
|
def test_accept_prefixed_file_paths(self):
|
|
67
72
|
self.prefix = '/a/b'
|
|
68
73
|
self.docs.append("video:c.mp4")
|
clams-python-1.1.6/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.1.6
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/.dockerignore.template
RENAMED
|
File without changes
|
|
File without changes
|
{clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/Containerfile.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/requirements.txt.template
RENAMED
|
File without changes
|
{clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/for-clams-team.md.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/publish.yml.template
RENAMED
|
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
|