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.
Files changed (41) hide show
  1. {clams-python-1.1.6/clams_python.egg-info → clams_python-1.2.1}/PKG-INFO +1 -1
  2. clams_python-1.2.1/VERSION +1 -0
  3. {clams-python-1.1.6 → clams_python-1.2.1}/clams/app/__init__.py +8 -11
  4. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/app.py.template +1 -2
  5. {clams-python-1.1.6 → clams_python-1.2.1}/clams/mmif_utils/source.py +32 -75
  6. clams_python-1.2.1/clams/ver/__init__.py +1 -0
  7. {clams-python-1.1.6 → clams_python-1.2.1/clams_python.egg-info}/PKG-INFO +1 -1
  8. {clams-python-1.1.6 → clams_python-1.2.1}/tests/test_clamsapp.py +20 -1
  9. {clams-python-1.1.6 → clams_python-1.2.1}/tests/test_clamscli.py +5 -0
  10. clams-python-1.1.6/VERSION +0 -1
  11. clams-python-1.1.6/clams/ver/__init__.py +0 -1
  12. {clams-python-1.1.6 → clams_python-1.2.1}/LICENSE +0 -0
  13. {clams-python-1.1.6 → clams_python-1.2.1}/MANIFEST.in +0 -0
  14. {clams-python-1.1.6 → clams_python-1.2.1}/README.md +0 -0
  15. {clams-python-1.1.6 → clams_python-1.2.1}/clams/__init__.py +0 -0
  16. {clams-python-1.1.6 → clams_python-1.2.1}/clams/appmetadata/__init__.py +0 -0
  17. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/__init__.py +0 -0
  18. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/.dockerignore.template +0 -0
  19. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/.gitignore.template +0 -0
  20. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/Containerfile.template +0 -0
  21. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/LICENSE.template +0 -0
  22. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/README.md.template +0 -0
  23. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/metadata.py.template +0 -0
  24. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/app/requirements.txt.template +0 -0
  25. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
  26. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
  27. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
  28. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
  29. {clams-python-1.1.6 → clams_python-1.2.1}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
  30. {clams-python-1.1.6 → clams_python-1.2.1}/clams/mmif_utils/__init__.py +0 -0
  31. {clams-python-1.1.6 → clams_python-1.2.1}/clams/mmif_utils/rewind.py +0 -0
  32. {clams-python-1.1.6 → clams_python-1.2.1}/clams/restify/__init__.py +0 -0
  33. {clams-python-1.1.6 → clams_python-1.2.1}/clams/serve/__init__.py +0 -0
  34. {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/SOURCES.txt +0 -0
  35. {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/dependency_links.txt +0 -0
  36. {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/entry_points.txt +0 -0
  37. {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/requires.txt +0 -0
  38. {clams-python-1.1.6 → clams_python-1.2.1}/clams_python.egg-info/top_level.txt +0 -0
  39. {clams-python-1.1.6 → clams_python-1.2.1}/requirements.txt +0 -0
  40. {clams-python-1.1.6 → clams_python-1.2.1}/setup.cfg +0 -0
  41. {clams-python-1.1.6 → clams_python-1.2.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clams-python
3
- Version: 1.1.6
3
+ Version: 1.2.1
4
4
  Summary: A collection of APIs to develop CLAMS app for python
5
5
  Home-page: https://clams.ai
6
6
  Author: Brandeis Lab for Linguistics and Computation
@@ -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: Optional[dict] = None) -> None:
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 self.metadata.parameters and self.metadata.parameters[orik].multivalued:
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
- view.metadata.add_app_configuration(k, v)
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 self.metadata.parameters and self.metadata.parameters[k].multivalued:
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: Union[str, dict, Mmif], **parameters) -> 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 and not path.isabs(prefix):
191
- raise ValueError(f"prefix must be an absolute path; given \"{prefix}\".")
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
- if prefix and path.isabs(location):
204
- raise ValueError(f"when prefix is used, file location must not be an absolute path; given \"{location}\".")
205
- elif not prefix and not path.isabs(location):
206
- raise ValueError(f'file location must be an absolute path, or --prefix must be used; given \"{location}\".')
207
- elif prefix and not path.isabs(location):
208
- location = path.join(prefix, location)
209
- doc = template.substitute(
210
- at_type=str(at_types[mime.split('/', maxsplit=1)[0]]),
211
- aid=f'd{doc_id}',
212
- mime=mime,
213
- location=location
214
- )
215
- pl.add_document(doc)
216
- return pl.produce().serialize(pretty=True)
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="This list of documents should be colon-joined pairs of document types and file paths. A document type "
278
- "can be one of ``audio``, ``video``, ``text``, ``image``, or a MIME type string (such as video/mp4). The "
279
- "output will be a MMIF file containing a document for each of those file paths, with the appropriate "
280
- "``@type`` and MIME type (if given), printed to the standard output. "
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` scheme, ignored otherwise). If '
288
- 'prefix is set, document file paths MUST be relative. Useful when creating source MMIF files from a '
289
- 'system that\'s different from the system that actually runs the workflow (e.g. in a container).'
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
- if args.scheme == 'file':
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clams-python
3
- Version: 1.1.6
3
+ Version: 1.2.1
4
4
  Summary: A collection of APIs to develop CLAMS app for python
5
5
  Home-page: https://clams.ai
6
6
  Author: Brandeis Lab for Linguistics and Computation
@@ -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")
@@ -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