clams-python 1.2.2__tar.gz → 1.2.4__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 (42) hide show
  1. {clams_python-1.2.2/clams_python.egg-info → clams_python-1.2.4}/PKG-INFO +2 -2
  2. clams_python-1.2.4/VERSION +1 -0
  3. {clams_python-1.2.2 → clams_python-1.2.4}/clams/app/__init__.py +11 -1
  4. {clams_python-1.2.2 → clams_python-1.2.4}/clams/appmetadata/__init__.py +6 -3
  5. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/__init__.py +65 -14
  6. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/Containerfile.template +20 -6
  7. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/README.md.template +10 -10
  8. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/app.py.template +16 -4
  9. clams_python-1.2.4/clams/develop/templates/app/cli.py.template +92 -0
  10. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/metadata.py.template +4 -4
  11. {clams_python-1.2.2 → clams_python-1.2.4}/clams/mmif_utils/rewind.py +10 -21
  12. {clams_python-1.2.2 → clams_python-1.2.4}/clams/restify/__init__.py +1 -0
  13. clams_python-1.2.4/clams/ver/__init__.py +1 -0
  14. {clams_python-1.2.2 → clams_python-1.2.4/clams_python.egg-info}/PKG-INFO +2 -2
  15. {clams_python-1.2.2 → clams_python-1.2.4}/clams_python.egg-info/SOURCES.txt +1 -0
  16. {clams_python-1.2.2 → clams_python-1.2.4}/clams_python.egg-info/requires.txt +1 -1
  17. {clams_python-1.2.2 → clams_python-1.2.4}/requirements.txt +1 -1
  18. clams_python-1.2.2/VERSION +0 -1
  19. clams_python-1.2.2/clams/ver/__init__.py +0 -1
  20. {clams_python-1.2.2 → clams_python-1.2.4}/LICENSE +0 -0
  21. {clams_python-1.2.2 → clams_python-1.2.4}/MANIFEST.in +0 -0
  22. {clams_python-1.2.2 → clams_python-1.2.4}/README.md +0 -0
  23. {clams_python-1.2.2 → clams_python-1.2.4}/clams/__init__.py +0 -0
  24. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/.dockerignore.template +0 -0
  25. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/.gitignore.template +0 -0
  26. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/LICENSE.template +0 -0
  27. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/app/requirements.txt.template +0 -0
  28. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
  29. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
  30. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
  31. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
  32. {clams_python-1.2.2 → clams_python-1.2.4}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
  33. {clams_python-1.2.2 → clams_python-1.2.4}/clams/mmif_utils/__init__.py +0 -0
  34. {clams_python-1.2.2 → clams_python-1.2.4}/clams/mmif_utils/source.py +0 -0
  35. {clams_python-1.2.2 → clams_python-1.2.4}/clams/serve/__init__.py +0 -0
  36. {clams_python-1.2.2 → clams_python-1.2.4}/clams_python.egg-info/dependency_links.txt +0 -0
  37. {clams_python-1.2.2 → clams_python-1.2.4}/clams_python.egg-info/entry_points.txt +0 -0
  38. {clams_python-1.2.2 → clams_python-1.2.4}/clams_python.egg-info/top_level.txt +0 -0
  39. {clams_python-1.2.2 → clams_python-1.2.4}/setup.cfg +0 -0
  40. {clams_python-1.2.2 → clams_python-1.2.4}/setup.py +0 -0
  41. {clams_python-1.2.2 → clams_python-1.2.4}/tests/test_clamsapp.py +0 -0
  42. {clams_python-1.2.2 → clams_python-1.2.4}/tests/test_clamscli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clams-python
3
- Version: 1.2.2
3
+ Version: 1.2.4
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
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
14
14
  Requires-Python: >=3.8
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: mmif-python==1.0.14
17
+ Requires-Dist: mmif-python==1.0.16
18
18
  Requires-Dist: Flask>=2
19
19
  Requires-Dist: Flask-RESTful>=0.3.9
20
20
  Requires-Dist: gunicorn>=20
@@ -0,0 +1 @@
1
+ 1.2.4
@@ -20,6 +20,16 @@ logging.basicConfig(
20
20
  datefmt="%Y-%m-%d %H:%M:%S")
21
21
 
22
22
 
23
+ falsy_values = [
24
+ 'False',
25
+ 'false',
26
+ 'F',
27
+ 'f',
28
+ '0',
29
+ 0,
30
+ False
31
+ ]
32
+
23
33
  class ClamsApp(ABC):
24
34
  """
25
35
  An abstract class to define API's for ClamsApps. A CLAMS app should inherit
@@ -388,7 +398,7 @@ class ParameterCaster(object):
388
398
  """
389
399
  Helper function to convert string values to bool type.
390
400
  """
391
- return False if value in (False, 0, 'False', 'false', '0') else True
401
+ return False if value in falsy_values else True
392
402
 
393
403
  @staticmethod
394
404
  def float_param(value) -> float:
@@ -162,7 +162,8 @@ class RuntimeParameter(_BaseModel):
162
162
  type: param_value_types = pydantic.Field(
163
163
  ...,
164
164
  description=f"Type of the parameter value the app expects. Must be one of {param_value_types_values}. When "
165
- "type is ``map``, ``multivalued=true`` is automatically forced. \n\n"
165
+ "type is ``map``, ``multivalued=true`` is forced, and when ``boolean``, ``multivalued=false`` is "
166
+ "forced. \n\n"
166
167
  "Notes for developers: \n\n"
167
168
  "When the type is ``map``, the parameter value (still a single string from the users' perspective) "
168
169
  "must be formatted as a ``KEY:VALUE`` pair, namely a colon-separated string. To pass multiple "
@@ -186,7 +187,7 @@ class RuntimeParameter(_BaseModel):
186
187
  description="(optional) Default value for the parameter.\n\n"
187
188
  "Notes for developers: \n\n"
188
189
  "Setting a default value makes a parameter `optional`. \n\n"
189
- "When ``multivalued=True``, the default value should be a list of values. \n\n"
190
+ "When ``multivalued=true``, the default value should be a list of values. \n\n"
190
191
  "When ``type=map``, the default value should be a list of colon-separated strings. \n\n"
191
192
  )
192
193
  multivalued: bool = pydantic.Field(
@@ -420,8 +421,10 @@ class AppMetadata(pydantic.BaseModel):
420
421
  # see https://docs.pydantic.dev/1.10/usage/types/#unions
421
422
  # e.g. casting 0.1 using the `primitives` dict will result in 0 (int)
422
423
  # while casting "0.1" using the `primitives` dict will result in 0.1 (float)
423
- if type == 'map' and multivalued is False:
424
+ if type == 'map':
424
425
  multivalued = True
426
+ if type == 'boolean':
427
+ multivalued = False
425
428
  if default is not None:
426
429
  if isinstance(default, list):
427
430
  default = [str(d) for d in default]
@@ -7,6 +7,7 @@ from typing import List
7
7
 
8
8
  import clams
9
9
 
10
+ update_tmp_suffix = '.tmp'
10
11
  available_recipes = {
11
12
  'app': {
12
13
  'description': 'Skeleton code for a CLAMS app',
@@ -26,7 +27,7 @@ class CookieCutter(object):
26
27
  def __init__(self, name: str, outdir: str, recipes: List[str]):
27
28
  self.rawname = name
28
29
  self.name_tokens = self.tokenize_rawname()
29
- self.ourdir = pathlib.Path(outdir)
30
+ self.outdir = pathlib.Path(outdir)
30
31
  if recipes:
31
32
  self.recipes = recipes
32
33
  else:
@@ -42,24 +43,30 @@ class CookieCutter(object):
42
43
  words.pop()
43
44
  return words
44
45
 
45
- def bake(self):
46
+ def bake(self, update_level=0):
46
47
  print(f"Baking {self.recipes}")
47
48
  for recipe in self.recipes:
48
49
  src_dir = pathlib.Path(__file__).parent / 'templates' / available_recipes[recipe]['sourcedir']
49
- dst_dir = self.ourdir / self.rawname / available_recipes[recipe]['targetdir']
50
+ dst_dir = self.outdir / self.rawname / available_recipes[recipe]['targetdir']
50
51
  if recipe == 'app':
51
- self.bake_app(src_dir, dst_dir)
52
+ caps = [t.capitalize() for t in self.name_tokens]
53
+ app_vars = {
54
+ 'CLAMS_VERSION': clams.__version__,
55
+ 'APP_CLASS_NAME': "".join(caps),
56
+ 'APP_NAME': " ".join(caps),
57
+ 'APP_IDENTIFIER': '-'.join(self.name_tokens)
58
+ }
59
+ if update_level > 0:
60
+ self.reheat_app(src_dir, dst_dir, app_vars, reheat_level=update_level)
61
+ else:
62
+ if dst_dir.exists():
63
+ raise FileExistsError(f" {dst_dir} already exists. Did you mean `--update`? ")
64
+ self.bake_app(src_dir, dst_dir, app_vars)
52
65
  if recipe == 'gha':
66
+ # There's nothing for devs to tweak GHA template, so first generation and updating are the same.
53
67
  self.bake_gha(src_dir, dst_dir)
54
68
 
55
- def bake_app(self, src_dir, dst_dir):
56
- caps = [t.capitalize() for t in self.name_tokens]
57
- templating_vars = {
58
- 'CLAMS_VERSION': clams.__version__,
59
- 'APP_CLASS_NAME': "".join(caps),
60
- 'APP_NAME': " ".join(caps),
61
- 'APP_IDENTIFIER': '-'.join(self.name_tokens)
62
- }
69
+ def bake_app(self, src_dir, dst_dir, templating_vars):
63
70
  for g in src_dir.glob("**/*.template"):
64
71
  r = g.relative_to(src_dir).parent
65
72
  f = g.with_suffix('').name
@@ -71,7 +78,40 @@ class CookieCutter(object):
71
78
  out_f.write(compiled)
72
79
  print(f"App skeleton code is copied to {self.rawname}")
73
80
  print(f" Checkout {self.rawname}/README.md for the next steps!")
74
-
81
+
82
+ def reheat_app(self, src_dir, dst_dir, templating_vars, reheat_level=1):
83
+ essentials = ['app.py', 'metadata.py', 'cli.py', 'Containerfile', 'requirements.txt']
84
+ for template in src_dir.glob("**/*.template"):
85
+ dirname = template.relative_to(src_dir).parent
86
+ basename = template.with_suffix('').name
87
+ if basename not in essentials:
88
+ # if non-essential, just skip when updating
89
+ continue
90
+ in_f = open(template, 'r')
91
+ tmpl_to_compile = Template(in_f.read())
92
+ compiled = tmpl_to_compile.safe_substitute(templating_vars)
93
+ in_f.close()
94
+ ori_fpath = dst_dir / dirname / basename
95
+ if not ori_fpath.exists():
96
+ # this file is new in this version of cookiecutter
97
+ with open(ori_fpath, 'w') as out_f:
98
+ out_f.write(compiled)
99
+ else:
100
+ ori_f = open(ori_fpath, 'r')
101
+ ori_content = ori_f.read()
102
+ if ori_content != compiled:
103
+ # when the target file already exists, we need to do diff & patch
104
+ # TODO (krim @ 5/5/24): add update level 2 and 3 code here
105
+ out_fpath = f'{ori_fpath}{update_tmp_suffix}'
106
+ print(f' {dst_dir / dirname / basename} already exists, generating a tmp file: {out_fpath}')
107
+ with open(out_fpath, 'w') as out_f:
108
+ out_f.write(compiled)
109
+ else:
110
+ print(f' {dst_dir / dirname / basename} already exists, but the content is unchanged from the '
111
+ f'template, skipping re-generating')
112
+ print(f"App skeleton code is updated in {self.rawname}")
113
+ print(f" Checkout {self.rawname}/README.md for the next steps!")
114
+
75
115
  def bake_gha(self, src_dir, dst_dir):
76
116
  self.simple_recursive_copy_minus_template_suffix(src_dir, dst_dir)
77
117
  print(f"GitHub Actions workflow files are copied to {self.rawname}/.github")
@@ -128,12 +168,23 @@ def prep_argparser(**kwargs):
128
168
  nargs='?',
129
169
  help='The name of the parent directory where the app skeleton directory is placed. (default: current directory)'
130
170
  )
171
+ parser.add_argument(
172
+ '-u', '--update',
173
+ action='count',
174
+ default=0, # > Note, the default will be None unless explicitly set to 0. (https://docs.python.org/3/library/argparse.html#action)
175
+ help=f'Set update level by passing this flag multiple times. This is EXPERIMENTAL, and developers MUST NOT'
176
+ f'rely on the update results, and should conduct manual checks afterward. LEVEL 0: does not update and '
177
+ f'raise an error when existing directory found. LEVEL 1: generate non-existing files and generate '
178
+ f'`{update_tmp_suffix}`-suffixed files for existing one. LEVEL 2 (WIP): generate non-existing files and '
179
+ f'automatically generate patch files for existing files. LEVEL 3 (WIP): generate non-existing files and '
180
+ f'apply patches to existing files. (default: 0)'
181
+ )
131
182
  return parser
132
183
 
133
184
 
134
185
  def main(args):
135
186
  cutter = CookieCutter(name=args.name, outdir=args.parent_dir, recipes=args.recipes)
136
- cutter.bake()
187
+ cutter.bake(args.update)
137
188
 
138
189
  if __name__ == '__main__':
139
190
  parser = prep_argparser()
@@ -1,11 +1,11 @@
1
1
  # Use the same base image version as the clams-python python library version
2
2
  FROM ghcr.io/clamsproject/clams-python:$CLAMS_VERSION
3
3
  # See https://github.com/orgs/clamsproject/packages?tab=packages&q=clams-python for more base images
4
- # IF you want to automatically publish this image to the clamsproject organization,
4
+ # IF you want to automatically publish this image to the clamsproject organization,
5
5
  # 1. you should have generated this template without --no-github-actions flag
6
- # 1. to add arm64 support, change relevant line in .github/workflows/container.yml
7
- # * NOTE that a lots of software doesn't install/compile or run on arm64 architecture out of the box
8
- # * make sure you locally test the compatibility of all software dependencies before using arm64 support
6
+ # 1. to add arm64 support, change relevant line in .github/workflows/container.yml
7
+ # * NOTE that a lots of software doesn't install/compile or run on arm64 architecture out of the box
8
+ # * make sure you locally test the compatibility of all software dependencies before using arm64 support
9
9
  # 1. use a git tag to trigger the github action. You need to use git tag to properly set app version anyway
10
10
 
11
11
  ################################################################################
@@ -14,6 +14,20 @@ ARG CLAMS_APP_VERSION
14
14
  ENV CLAMS_APP_VERSION ${CLAMS_APP_VERSION}
15
15
  ################################################################################
16
16
 
17
+ ################################################################################
18
+ # This is duplicate from the base image Containerfile
19
+ # but makes sure the cache directories are consistent across all CLAMS apps
20
+
21
+ # https://github.com/openai/whisper/blob/ba3f3cd54b0e5b8ce1ab3de13e32122d0d5f98ab/whisper/__init__.py#L130
22
+ ENV XDG_CACHE_HOME='/cache'
23
+ # https://huggingface.co/docs/huggingface_hub/main/en/package_reference/environment_variables#hfhome
24
+ ENV HF_HOME="/cache/huggingface"
25
+ # https://pytorch.org/docs/stable/hub.html#where-are-my-downloaded-models-saved
26
+ ENV TORCH_HOME="/cache/torch"
27
+
28
+ RUN mkdir /cache && rm -rf /root/.cache && ln -s /cache /root/.cache
29
+ ################################################################################
30
+
17
31
  ################################################################################
18
32
  # clams-python base images are based on debian distro
19
33
  # install more system packages as needed using the apt manager
@@ -23,8 +37,8 @@ ENV CLAMS_APP_VERSION ${CLAMS_APP_VERSION}
23
37
  # main app installation
24
38
  COPY ./ /app
25
39
  WORKDIR /app
26
- RUN pip3 install -r requirements.txt
40
+ RUN pip3 install --no-cache-dir -r requirements.txt
27
41
 
28
- # default command to run the CLAMS app in a production server
42
+ # default command to run the CLAMS app in a production server
29
43
  CMD ["python3", "app.py", "--production"]
30
44
  ################################################################################
@@ -1,33 +1,33 @@
1
1
  # TO_DEVS: BURN AFTER READING
2
2
 
3
- Delete this section of the document once the app development is done, before publishing the repository.
3
+ Delete this section of the document once the app development is done, before publishing the repository.
4
4
 
5
5
  ---
6
- This skeleton code is a scaffolding for Python-based CLAMS app development. Specifically, it contains
6
+ This skeleton code is a scaffolding for Python-based CLAMS app development. Specifically, it contains
7
7
 
8
- 1. `app.py` and `metadata.py` to write the app
8
+ 1. `app.py` and `metadata.py` to write the app
9
9
  1. `requirements.txt` to specify python dependencies
10
10
  1. `Containerfile` to containerize the app and specify system dependencies
11
11
  1. `.gitignore` and `.dockerignore` files listing commonly ignored files
12
12
  1. an empty `LICENSE` file to replace with an actual license information of the app
13
13
  1. This `README.md` file for additional information not specified in the general user manual at https://apps.clams.ai/clamsapp
14
- 1. A number of GitHub Actions workflows for issue/bug-report management
14
+ 1. A number of GitHub Actions workflows for issue/bugreport management
15
15
  1. A GHA workflow to publish app images upon any push of a git tag
16
16
  * **NOTE**: All GHA workflows included are designed to only work in repositories under `clamsproject` organization.
17
17
 
18
18
  Before pushing your first commit, please make sure to delete this section of the document.
19
19
 
20
- Then use the following section to document any additional information specific to this app. If your app works significantly different from what's described in the generic readme file, be as specific as possible.
20
+ Then use the following section to document any additional information specific to this app. If your app works significantly different from what's described in the generic readme file, be as specific as possible.
21
21
 
22
22
 
23
- > **warning**
23
+ > **warning**
24
24
  > TO_DEVS: Delete these `TO_DEVS` notes and warnings before publishing the repository.
25
25
 
26
26
  ---
27
27
 
28
28
  # $APP_NAME
29
29
 
30
- > **warning**
30
+ > **warning**
31
31
  > TO_DEVS: Again, delete these `TO_DEVS` notes and warnings before publishing the repository.
32
32
 
33
33
  ## Description
@@ -37,11 +37,11 @@ Then use the following section to document any additional information specific t
37
37
 
38
38
  ## User instruction
39
39
 
40
- General user instructions for CLAMS apps is available at [CLAMS Apps documentation](https://apps.clams.ai/clamsapp).
40
+ General user instructions for CLAMS apps are available at [CLAMS Apps documentation](https://apps.clams.ai/clamsapp).
41
41
 
42
42
  Below is a list of additional information specific to this app.
43
43
 
44
- > **note**
44
+ > **note**
45
45
  > TO_DEVS: Below is a list of additional information specific to this app.
46
46
 
47
47
 
@@ -57,7 +57,7 @@ Below is a list of additional information specific to this app.
57
57
 
58
58
  ### Configurable runtime parameter
59
59
 
60
- For the full list of parameters, please refer to the app metadata from [CLAMS App Directory](https://apps.clams.ai) or [`metadata.py`](metadata.py) file in this repository.
60
+ For the full list of parameters, please refer to the app metadata from the [CLAMS App Directory](https://apps.clams.ai) or the [`metadata.py`](metadata.py) file in this repository.
61
61
 
62
62
  > **warning**
63
63
  > TO_DEVS: If you're not developing this app for publishing on the CLAMS App Directory, the above paragraph is not applicable. Feel free to delete or change it.
@@ -6,7 +6,7 @@ app.py Template
6
6
  The app.py script does several things:
7
7
  - import the necessary code
8
8
  - create a subclass of ClamsApp that defines the metadata and provides a method to run the wrapped NLP tool
9
- - provide a way to run the code as a RESTful Flask service
9
+ - provide a way to run the code as a RESTful Flask service
10
10
 
11
11
 
12
12
  """
@@ -31,14 +31,24 @@ class $APP_CLASS_NAME(ClamsApp):
31
31
 
32
32
  def _appmetadata(self):
33
33
  # see https://sdk.clams.ai/autodoc/clams.app.html#clams.app.ClamsApp._load_appmetadata
34
- # Also check out ``metadata.py`` in this directory.
35
- # When using the ``metadata.py`` leave this do-nothing "pass" method here.
34
+ # Also check out ``metadata.py`` in this directory.
35
+ # When using the ``metadata.py`` leave this do-nothing "pass" method here.
36
36
  pass
37
37
 
38
38
  def _annotate(self, mmif: Mmif, **parameters) -> Mmif:
39
39
  # see https://sdk.clams.ai/autodoc/clams.app.html#clams.app.ClamsApp._annotate
40
40
  raise NotImplementedError
41
41
 
42
+ def get_app():
43
+ """
44
+ This function effectively creates an instance of the app class, without any arguments passed in, meaning, any
45
+ external information such as initial app configuration should be set without using function arguments. The easiest
46
+ way to do this is to set global variables before calling this.
47
+ """
48
+ # for example:
49
+ # return $APP_CLASS_NAME(create, from, global, params)
50
+ raise NotImplementedError
51
+
42
52
 
43
53
  if __name__ == "__main__":
44
54
  parser = argparse.ArgumentParser()
@@ -50,7 +60,9 @@ if __name__ == "__main__":
50
60
  parsed_args = parser.parse_args()
51
61
 
52
62
  # create the app instance
53
- app = $APP_CLASS_NAME()
63
+ # if get_app() call requires any "configurations", they should be set now as global variables
64
+ # and referenced in the get_app() function. NOTE THAT you should not change the signature of get_app()
65
+ app = get_app()
54
66
 
55
67
  http_app = Restifier(app, port=int(parsed_args.port))
56
68
  # for running the application in production mode
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ The purpose of this file is to define a thin CLI interface for your app
4
+
5
+ DO NOT CHANGE the name of the file
6
+ """
7
+
8
+ import argparse
9
+ import sys
10
+ from contextlib import redirect_stdout
11
+
12
+ import app
13
+
14
+ import clams.app
15
+ from clams import AppMetadata
16
+
17
+
18
+ def metadata_to_argparser(app_metadata: AppMetadata) -> argparse.ArgumentParser:
19
+ """
20
+ Automatically generate an argparse.ArgumentParser from parameters specified in the app metadata (metadata.py).
21
+ """
22
+
23
+ parser = argparse.ArgumentParser(
24
+ description=f"{app_metadata.name}: {app_metadata.description} (visit {app_metadata.url} for more info)",
25
+ formatter_class=argparse.RawDescriptionHelpFormatter)
26
+
27
+ # parse cli args from app parameters
28
+ for parameter in app_metadata.parameters:
29
+ if parameter.multivalued:
30
+ a = parser.add_argument(
31
+ f"--{parameter.name}",
32
+ help=parameter.description,
33
+ nargs='+',
34
+ action='extend',
35
+ type=str
36
+ )
37
+ else:
38
+ a = parser.add_argument(
39
+ f"--{parameter.name}",
40
+ help=parameter.description,
41
+ nargs=1,
42
+ action="store",
43
+ type=str)
44
+ if parameter.choices is not None:
45
+ a.choices = parameter.choices
46
+ if parameter.default is not None:
47
+ a.help += f" (default: {parameter.default}"
48
+ if parameter.type == "boolean":
49
+ a.help += (f", any value except for {[v for v in clams.app.falsy_values if isinstance(v, str)]} "
50
+ f"will be interpreted as True")
51
+ a.help += ')'
52
+ # then we don't have to add default values to the arg_parser
53
+ # since that's handled by the app._refined_params() method.
54
+ parser.add_argument('IN_MMIF_FILE', nargs='?', type=argparse.FileType('r'),
55
+ help='input MMIF file path, or STDIN if `-` or not provided. NOTE: When running this cli.py in '
56
+ 'a containerized environment, make sure the container is run with `-i` flag to keep stdin '
57
+ 'open.',
58
+ # will check if stdin is a keyboard, and return None if it is
59
+ default=None if sys.stdin.isatty() else sys.stdin)
60
+ parser.add_argument('OUT_MMIF_FILE', nargs='?', type=argparse.FileType('w'),
61
+ help='output MMIF file path, or STDOUT if `-` or not provided. NOTE: When this is set to '
62
+ 'STDOUT, any print statements in the app code will be redirected to stderr.',
63
+ default=sys.stdout)
64
+ return parser
65
+
66
+
67
+ if __name__ == "__main__":
68
+ clamsapp = app.get_app()
69
+ arg_parser = metadata_to_argparser(app_metadata=clamsapp.metadata)
70
+ args = arg_parser.parse_args()
71
+ if args.IN_MMIF_FILE:
72
+ in_data = args.IN_MMIF_FILE.read()
73
+ # since flask webapp interface will pass parameters as "unflattened" dict to handle multivalued parameters
74
+ # (https://werkzeug.palletsprojects.com/en/latest/datastructures/#werkzeug.datastructures.MultiDict.to_dict)
75
+ # we need to convert arg_parsers results into a similar structure, which is the dict values are wrapped in lists
76
+ params = {}
77
+ for pname, pvalue in vars(args).items():
78
+ if pvalue is None or pname in ['IN_MMIF_FILE', 'OUT_MMIF_FILE']:
79
+ continue
80
+ elif isinstance(pvalue, list):
81
+ params[pname] = pvalue
82
+ else:
83
+ params[pname] = [pvalue]
84
+ if args.OUT_MMIF_FILE.name == '<stdout>':
85
+ with redirect_stdout(sys.stderr):
86
+ out_mmif = clamsapp.annotate(in_data, **params)
87
+ else:
88
+ out_mmif = clamsapp.annotate(in_data, **params)
89
+ args.OUT_MMIF_FILE.write(out_mmif)
90
+ else:
91
+ arg_parser.print_help()
92
+ sys.exit(1)
@@ -1,5 +1,5 @@
1
1
  """
2
- The purpose of this file is to define the metadata of the app with minimal imports.
2
+ The purpose of this file is to define the metadata of the app with minimal imports.
3
3
 
4
4
  DO NOT CHANGE the name of the file
5
5
  """
@@ -10,12 +10,12 @@ from clams.app import ClamsApp
10
10
  from clams.appmetadata import AppMetadata
11
11
 
12
12
 
13
- # DO NOT CHANGE the function name
13
+ # DO NOT CHANGE the function name
14
14
  def appmetadata() -> AppMetadata:
15
15
  """
16
16
  Function to set app-metadata values and return it as an ``AppMetadata`` obj.
17
17
  Read these documentations before changing the code below
18
- - https://sdk.clams.ai/appmetadata.html metadata specification.
18
+ - https://sdk.clams.ai/appmetadata.html metadata specification.
19
19
  - https://sdk.clams.ai/autodoc/clams.appmetadata.html python API
20
20
 
21
21
  :return: AppMetadata object holding all necessary information.
@@ -26,7 +26,7 @@ def appmetadata() -> AppMetadata:
26
26
  name="$APP_NAME",
27
27
  description="", # briefly describe what the purpose and features of the app
28
28
  app_license="", # short name for a software license like MIT, Apache2, GPL, etc.
29
- identifier="$APP_IDENTIFIER", # should be a single string without whitespaces. If you don't intent to publish this app to the CLAMS app-directory, please use a full IRI format.
29
+ identifier="$APP_IDENTIFIER", # should be a single string without whitespaces. If you don't intent to publish this app to the CLAMS app-directory, please use a full IRI format.
30
30
  url="https://fakegithub.com/some/repository", # a website where the source code and full documentation of the app is hosted
31
31
  # (if you are on the CLAMS team, this MUST be "https://github.com/clamsproject/app-$APP_IDENTIFIER"
32
32
  # (see ``.github/README.md`` file in this directory for the reason)
@@ -77,10 +77,14 @@ def describe_argparser():
77
77
  def prep_argparser(**kwargs):
78
78
  parser = argparse.ArgumentParser(description=describe_argparser()[1],
79
79
  formatter_class=argparse.RawDescriptionHelpFormatter, **kwargs)
80
- parser.add_argument("mmif_file",
81
- help="Path to the input MMIF file, or '-' to read from stdin.")
82
- parser.add_argument("-o", '--output', default=None, metavar="PATH",
83
- help="Path to the rewound MMIF output file. When not given, the rewound is printed to stdout.")
80
+ parser.add_argument("IN_MMIF_FILE",
81
+ nargs="?", type=argparse.FileType("r"),
82
+ default=None if sys.stdin.isatty() else sys.stdin,
83
+ help='input MMIF file path, or STDIN if `-` or not provided.')
84
+ parser.add_argument("OUT_MMIF_FILE",
85
+ nargs="?", type=argparse.FileType("w"),
86
+ default=sys.stdout,
87
+ help='output MMIF file path, or STDOUT if `-` or not provided.')
84
88
  parser.add_argument("-p", '--pretty', action='store_true',
85
89
  help="Pretty-print rewound MMIF")
86
90
  parser.add_argument("-n", '--number', default="0", type=int,
@@ -92,7 +96,7 @@ def prep_argparser(**kwargs):
92
96
 
93
97
 
94
98
  def main(args):
95
- mmif_obj = mmif.Mmif(str(sys.stdin)) if args.mmif_file[0] == '-' else mmif.Mmif(open(args.mmif_file).read())
99
+ mmif_obj = mmif.Mmif(args.IN_MMIF_FILE.read())
96
100
 
97
101
  if args.number == 0: # If user doesn't know how many views to rewind, give them choices.
98
102
  choice = prompt_user(mmif_obj)
@@ -101,22 +105,7 @@ def main(args):
101
105
  if not isinstance(choice, int) or choice <= 0:
102
106
  raise ValueError(f"Only can rewind by a positive number of views. Got {choice}.")
103
107
 
104
- if args.output:
105
- # Check if the same file name exist in the path and avoid overwriting.
106
- output_fp = P(args.output)
107
- if output_fp.is_file():
108
- parent = output_fp.parent
109
- stem = output_fp.stem
110
- suffix = output_fp.suffix
111
- count = 1
112
- while (parent / f"{stem}_{count}{suffix}").is_file():
113
- count += 1
114
- output_fp = parent / f"{stem}_{count}{suffix}"
115
-
116
- out_f = open(output_fp, 'w')
117
- else:
118
- out_f = sys.stdout
119
- out_f.write(rewind_mmif(mmif_obj, choice, args.mode == 'view').serialize(pretty=args.pretty))
108
+ args.OUT_MMIF_FILE.write(rewind_mmif(mmif_obj, choice, args.mode == 'view').serialize(pretty=args.pretty))
120
109
 
121
110
 
122
111
  if __name__ == "__main__":
@@ -145,6 +145,7 @@ class ClamsHTTPApi(Resource):
145
145
  try:
146
146
  return self.json_to_response(self.cla.annotate(raw_data, **raw_params))
147
147
  except Exception:
148
+ self.cla.logger.exception("Error in annotation")
148
149
  return self.json_to_response(self.cla.record_error(raw_data, **raw_params).serialize(pretty=True), status=500)
149
150
 
150
151
  put = post
@@ -0,0 +1 @@
1
+ __version__ = "1.2.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clams-python
3
- Version: 1.2.2
3
+ Version: 1.2.4
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
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
14
14
  Requires-Python: >=3.8
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: mmif-python==1.0.14
17
+ Requires-Dist: mmif-python==1.0.16
18
18
  Requires-Dist: Flask>=2
19
19
  Requires-Dist: Flask-RESTful>=0.3.9
20
20
  Requires-Dist: gunicorn>=20
@@ -14,6 +14,7 @@ clams/develop/templates/app/Containerfile.template
14
14
  clams/develop/templates/app/LICENSE.template
15
15
  clams/develop/templates/app/README.md.template
16
16
  clams/develop/templates/app/app.py.template
17
+ clams/develop/templates/app/cli.py.template
17
18
  clams/develop/templates/app/metadata.py.template
18
19
  clams/develop/templates/app/requirements.txt.template
19
20
  clams/develop/templates/gha/for-clams-team.md.template
@@ -1,4 +1,4 @@
1
- mmif-python==1.0.14
1
+ mmif-python==1.0.16
2
2
  Flask>=2
3
3
  Flask-RESTful>=0.3.9
4
4
  gunicorn>=20
@@ -1,4 +1,4 @@
1
- mmif-python==1.0.14
1
+ mmif-python==1.0.16
2
2
 
3
3
  Flask>=2
4
4
  Flask-RESTful>=0.3.9
@@ -1 +0,0 @@
1
- 1.2.2
@@ -1 +0,0 @@
1
- __version__ = "1.2.2"
File without changes
File without changes
File without changes
File without changes
File without changes