clams-python 1.7.4__tar.gz → 1.8.0__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.7.4 → clams_python-1.8.0}/.gitignore +1 -1
- {clams_python-1.7.4 → clams_python-1.8.0}/CHANGELOG.md +13 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/CONTRIBUTING.md +1 -1
- {clams_python-1.7.4/clams_python.egg-info → clams_python-1.8.0}/PKG-INFO +4 -4
- {clams_python-1.7.4 → clams_python-1.8.0}/build-tools/clean.py +1 -1
- {clams_python-1.7.4 → clams_python-1.8.0}/build-tools/test.py +4 -4
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/__init__.py +1 -1
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/app/__init__.py +257 -39
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/appmetadata/__init__.py +18 -4
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/backends/hf.py +4 -4
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/__init__.py +1 -1
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/Containerfile.template +0 -14
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/utl-tf/timeframe.py.template +40 -21
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/envelop/__init__.py +1 -1
- {clams_python-1.7.4 → clams_python-1.8.0/clams_python.egg-info}/PKG-INFO +4 -4
- {clams_python-1.7.4 → clams_python-1.8.0}/clams_python.egg-info/SOURCES.txt +1 -1
- {clams_python-1.7.4 → clams_python-1.8.0}/clams_python.egg-info/requires.txt +3 -3
- {clams_python-1.7.4 → clams_python-1.8.0}/clams_python.egg-info/scm_file_list.json +1 -1
- clams_python-1.8.0/clams_python.egg-info/scm_version.json +8 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/app-baseclasses.rst +65 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/target-versions.csv +1 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/pyproject.toml +18 -3
- clams_python-1.8.0/tests/test_develop.py +122 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/tests/test_promptable.py +261 -0
- clams_python-1.7.4/.pytype.cfg +0 -10
- clams_python-1.7.4/clams_python.egg-info/scm_version.json +0 -8
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/codecov.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/container.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/containers.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/issue-assign.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/issue-close.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/issue-infra-project.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/publish.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/.github/workflows/release-check.yml +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/LICENSE +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/README.md +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/build-tools/build.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/build-tools/docs.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/build-tools/prep_release.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/build-tools/publish.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/backends/__init__.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/.dockerignore.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/.gitignore.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/LICENSE.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/README.md.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/app.py.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/cli.py.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/metadata.py.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/app/requirements.txt.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/develop/templates/utl-tf/__init__.py.template +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/restify/__init__.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/serve/__init__.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams/ver/__init__.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams_python.egg-info/dependency_links.txt +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams_python.egg-info/entry_points.txt +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/clams_python.egg-info/top_level.txt +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/Containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/ffmpeg-hf.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/ffmpeg-hf4.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/ffmpeg-hf5.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/ffmpeg-tf2.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/ffmpeg-torch2.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/ffmpeg.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/generate_containers_yml.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/hf.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/hf4.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/hf5.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/jdk8.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/opencv4-hf.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/opencv4-hf4.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/opencv4-hf5.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/opencv4-tf2.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/opencv4-torch2.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/opencv4.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/tf2.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/container/torch2.containerfile +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/appdirectory.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/appmetadata.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/autodoc/clams.app.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/autodoc/clams.appmetadata.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/autodoc/clams.backends.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/autodoc/clams.mmif_utils.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/autodoc/clams.restify.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/clamsapp.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/cli.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/conf.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/gpu-apps.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/index.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/input-output.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/introduction.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/modules.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/runtime-params.rst +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/documentation/tutorial.md +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/setup.cfg +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/tests/metadata.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/tests/pillow-logo.png +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/tests/test_backends_hf.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/tests/test_clamsapp.py +0 -0
- {clams_python-1.7.4 → clams_python-1.8.0}/tests/test_envelope.py +0 -0
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
|
|
2
|
+
## releasing 1.7.4 (2026-09-01)
|
|
3
|
+
### Overview
|
|
4
|
+
|
|
5
|
+
Fixes parameter recording in signed view metadata, adds consistent handling of empty views, and repairs the `-torch` base container images.
|
|
6
|
+
|
|
7
|
+
### Changes
|
|
8
|
+
|
|
9
|
+
* `parameters`: fix recording of multi-valued and raw parameters when signing a view, and clarify the distinction between `params` and `appConfiguration` in view metadata (https://github.com/clamsproject/clams-python/issues/296)
|
|
10
|
+
* views: handle "empty" views returned from apps consistently and emit an SDK-level warning when an app returns no new annotations (https://github.com/clamsproject/clams-python/issues/305)
|
|
11
|
+
* `-torch` container images: add a minimal C tool chain to the base images for triton compatibility ( https://github.com/clamsproject/clams-python/issues/302)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
2
15
|
## releasing 1.7.3 (2026-07-09)
|
|
3
16
|
### Overview
|
|
4
17
|
This patch includes critical bugfixes. See changes below.
|
|
@@ -15,7 +15,7 @@ Unlike the old `setup.py`-based workflow, an editable install
|
|
|
15
15
|
(`pip install -e .`) is now required before running tests or building
|
|
16
16
|
docs. The package uses `importlib.metadata` for version resolution at
|
|
17
17
|
runtime, which only works when the package is registered in the
|
|
18
|
-
environment. You can no longer run `pytest` or `
|
|
18
|
+
environment. You can no longer run `pytest` or `mypy` directly
|
|
19
19
|
against the source tree without installing first. If you want to avoid
|
|
20
20
|
pulling in all dependencies, `pip install -e . --no-deps` is sufficient
|
|
21
21
|
to register the package metadata.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clams-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: A collection of APIs to develop CLAMS app for python
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: homepage, https://clams.ai
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
13
13
|
Requires-Python: >=3.10
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: mmif-python==1.5.
|
|
16
|
+
Requires-Dist: mmif-python==1.5.4
|
|
17
17
|
Requires-Dist: Flask>=2
|
|
18
18
|
Requires-Dist: Flask-RESTful>=0.3.9
|
|
19
19
|
Requires-Dist: gunicorn>=20
|
|
@@ -21,7 +21,7 @@ Requires-Dist: lapps>=0.0.2
|
|
|
21
21
|
Requires-Dist: pydantic>=2
|
|
22
22
|
Requires-Dist: jsonschema>=3
|
|
23
23
|
Provides-Extra: dev
|
|
24
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: mypy; extra == "dev"
|
|
25
25
|
Requires-Dist: pytest; extra == "dev"
|
|
26
26
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
27
27
|
Requires-Dist: pillow; extra == "dev"
|
|
@@ -32,7 +32,7 @@ Requires-Dist: furo; extra == "docs"
|
|
|
32
32
|
Requires-Dist: m2r2; extra == "docs"
|
|
33
33
|
Requires-Dist: sphinx-jsonschema; extra == "docs"
|
|
34
34
|
Provides-Extra: test
|
|
35
|
-
Requires-Dist:
|
|
35
|
+
Requires-Dist: mypy; extra == "test"
|
|
36
36
|
Requires-Dist: pytest; extra == "test"
|
|
37
37
|
Requires-Dist: pytest-cov; extra == "test"
|
|
38
38
|
Requires-Dist: pillow; extra == "test"
|
|
@@ -13,7 +13,7 @@ PROJECT_ROOT = SCRIPT_DIR.parent
|
|
|
13
13
|
# Directories to remove
|
|
14
14
|
CLEAN_DIRS = [
|
|
15
15
|
"build", "dist", "*.egg-info", "clams_python-*",
|
|
16
|
-
".pytest_cache", ".
|
|
16
|
+
".pytest_cache", ".mypy_cache", ".hypothesis",
|
|
17
17
|
"tests/.hypothesis", "htmlcov",
|
|
18
18
|
"docs-test",
|
|
19
19
|
]
|
|
@@ -3,7 +3,7 @@ Run tests for the clams-python package.
|
|
|
3
3
|
|
|
4
4
|
This script is equivalent to ``make test`` in the Makefile-based repos:
|
|
5
5
|
pip install -e ".[test]"
|
|
6
|
-
|
|
6
|
+
python -m mypy
|
|
7
7
|
python -m pytest --cov=clams --cov-report=xml
|
|
8
8
|
"""
|
|
9
9
|
import argparse
|
|
@@ -50,10 +50,10 @@ def main():
|
|
|
50
50
|
cwd=project_root,
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
-
# Run
|
|
54
|
-
print("\n--- Running
|
|
53
|
+
# Run mypy static analysis (configured in pyproject.toml)
|
|
54
|
+
print("\n--- Running mypy ---")
|
|
55
55
|
run_command(
|
|
56
|
-
[
|
|
56
|
+
[sys.executable, "-m", "mypy"],
|
|
57
57
|
cwd=project_root,
|
|
58
58
|
)
|
|
59
59
|
|
|
@@ -11,7 +11,7 @@ from clams.envelop import create_envelope
|
|
|
11
11
|
from clams.restify import Restifier
|
|
12
12
|
from clams.ver import __version__
|
|
13
13
|
|
|
14
|
-
__all__ = [AppMetadata, Restifier] + app_all
|
|
14
|
+
__all__ = ['AppMetadata', 'Restifier'] + app_all
|
|
15
15
|
version_template = "{} (based on MMIF spec: {})"
|
|
16
16
|
|
|
17
17
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import inspect
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
@@ -6,19 +7,22 @@ import sys
|
|
|
6
7
|
import warnings
|
|
7
8
|
from abc import ABC, abstractmethod
|
|
8
9
|
from contextlib import contextmanager
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
9
12
|
from datetime import datetime
|
|
10
13
|
from urllib import parse as urlparser
|
|
11
14
|
|
|
12
|
-
__all__ = ['ClamsApp', 'ClamsPromptableApp', 'ClamsHFPromptableApp'
|
|
15
|
+
__all__ = ['ClamsApp', 'ClamsPromptableApp', 'ClamsHFPromptableApp',
|
|
16
|
+
'PromptTask', 'ImagesPerPromptMode']
|
|
13
17
|
|
|
14
|
-
from typing import Union, Any, Optional, Dict, List, Tuple, cast
|
|
18
|
+
from typing import Union, Any, Optional, Dict, List, Mapping, Tuple, cast
|
|
15
19
|
|
|
16
20
|
from mmif import Mmif, Document, DocumentTypes, View, AnnotationTypes
|
|
17
21
|
from mmif.utils.video_document_helper import (
|
|
18
22
|
SamplingMode, SAMPLING_MODE_DESCRIPTIONS, SAMPLING_MODE_DEFAULT,
|
|
19
23
|
_sampling_mode,
|
|
20
24
|
)
|
|
21
|
-
from mmif.utils.workflow_helper import generate_param_hash
|
|
25
|
+
from mmif.utils.workflow_helper import generate_param_hash
|
|
22
26
|
from clams.appmetadata import AppMetadata, real_valued_primitives, python_type, map_param_kv_delimiter
|
|
23
27
|
from clams.envelop import unwrap_if_envelope
|
|
24
28
|
|
|
@@ -53,6 +57,47 @@ class EmptyOutputWarning(UserWarning):
|
|
|
53
57
|
"""Raised when an app's ``_annotate()`` produces no non-empty views."""
|
|
54
58
|
|
|
55
59
|
|
|
60
|
+
class ImagesPerPromptMode(Enum):
|
|
61
|
+
"""
|
|
62
|
+
How :py:meth:`ClamsPromptableApp.chunk_tasks` sizes the chunks of a
|
|
63
|
+
task that has more images than the cap. Both modes produce the same
|
|
64
|
+
number of chunks; they differ in how images are spread over them.
|
|
65
|
+
"""
|
|
66
|
+
#: Fill each chunk to the cap; the last chunk holds the remainder.
|
|
67
|
+
MAX = 'max'
|
|
68
|
+
#: Spread images evenly, so chunk sizes differ by at most one.
|
|
69
|
+
BALANCED = 'balanced'
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class PromptTask:
|
|
74
|
+
"""
|
|
75
|
+
One unit of promptable work: the images bundled into a single prompt,
|
|
76
|
+
the annotations they were drawn from, and the annotation the response
|
|
77
|
+
is anchored to.
|
|
78
|
+
|
|
79
|
+
``images`` and ``origins`` are parallel: ``origins[i]`` is the ``id``
|
|
80
|
+
of the annotation (typically a ``TimePoint``) that ``images[i]`` was
|
|
81
|
+
extracted from. ``source`` is the ``id`` of the coarse anchor
|
|
82
|
+
(typically the parent ``TimeFrame``). The fields map directly onto
|
|
83
|
+
:py:meth:`ClamsPromptableApp.response_to_grounded_textdocument`.
|
|
84
|
+
``source`` also serves as the join key for app-side data about the
|
|
85
|
+
anchor, such as the TimeFrame label.
|
|
86
|
+
|
|
87
|
+
:raises ValueError: if ``images`` and ``origins`` differ in length.
|
|
88
|
+
"""
|
|
89
|
+
images: List[Any]
|
|
90
|
+
origins: List[str]
|
|
91
|
+
source: str
|
|
92
|
+
|
|
93
|
+
def __post_init__(self):
|
|
94
|
+
if len(self.images) != len(self.origins):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"PromptTask images and origins must be parallel; got "
|
|
97
|
+
f"{len(self.images)} images and {len(self.origins)} "
|
|
98
|
+
f"origins for source {self.source!r}.")
|
|
99
|
+
|
|
100
|
+
|
|
56
101
|
class ClamsApp(ABC):
|
|
57
102
|
"""
|
|
58
103
|
An abstract class to define API's for ClamsApps. A CLAMS app should inherit
|
|
@@ -62,7 +107,7 @@ class ClamsApp(ABC):
|
|
|
62
107
|
# A set of "universal runtime parameters that can be used for both GET and POST anytime".
|
|
63
108
|
# The behavioral changes based on these parameters must be implemented on the SDK level.
|
|
64
109
|
# This needs to stay as a list of dicts for compatibility with the metadata.py file (templated).
|
|
65
|
-
universal_parameters = [
|
|
110
|
+
universal_parameters: List[Dict[str, Any]] = [
|
|
66
111
|
{
|
|
67
112
|
'name': 'pretty', 'type': 'boolean', 'choices': None, 'default': False, 'multivalued': False,
|
|
68
113
|
'description': 'The JSON body of the HTTP response will be re-formatted with 2-space indentation',
|
|
@@ -137,10 +182,10 @@ class ClamsApp(ABC):
|
|
|
137
182
|
For metadata specification,
|
|
138
183
|
see `https://clams.ai/clams-python/appmetadata.jsonschema <../appmetadata.jsonschema>`_.
|
|
139
184
|
"""
|
|
140
|
-
cwd = pathlib.Path(
|
|
185
|
+
cwd = pathlib.Path(inspect.getfile(type(self))).parent
|
|
141
186
|
|
|
142
187
|
if (cwd / 'metadata.py').exists():
|
|
143
|
-
import metadata as metadatapy
|
|
188
|
+
import metadata as metadatapy
|
|
144
189
|
metadata = metadatapy.appmetadata()
|
|
145
190
|
else:
|
|
146
191
|
metadata = self._appmetadata()
|
|
@@ -180,7 +225,7 @@ class ClamsApp(ABC):
|
|
|
180
225
|
mmif, runtime_params = unwrap_if_envelope(mmif, runtime_params)
|
|
181
226
|
mmif = Mmif(mmif)
|
|
182
227
|
existing_view_ids = {view.id for view in mmif.views}
|
|
183
|
-
issued_warnings = []
|
|
228
|
+
issued_warnings: List[Union[Warning, warnings.WarningMessage]] = []
|
|
184
229
|
for key in runtime_params:
|
|
185
230
|
if key not in self.annotate_param_spec:
|
|
186
231
|
issued_warnings.append(UserWarning(f'An undefined parameter "{key}" (value: "{runtime_params[key]}") is passed'))
|
|
@@ -214,7 +259,7 @@ class ClamsApp(ABC):
|
|
|
214
259
|
td = run_id - t
|
|
215
260
|
runningTime = refined.get('runningTime', False)
|
|
216
261
|
hwFetch = refined.get('hwFetch', False)
|
|
217
|
-
runtime_recs = {}
|
|
262
|
+
runtime_recs: Dict[str, Any] = {}
|
|
218
263
|
if hwFetch:
|
|
219
264
|
import multiprocessing
|
|
220
265
|
import platform, shutil, subprocess
|
|
@@ -237,7 +282,7 @@ class ClamsApp(ABC):
|
|
|
237
282
|
for annotated_view in annotated.views:
|
|
238
283
|
if annotated_view.id not in existing_view_ids and annotated_view.metadata.app == str(self.metadata.identifier):
|
|
239
284
|
annotated_view.metadata.timestamp = run_id
|
|
240
|
-
profiling_data = {}
|
|
285
|
+
profiling_data: Dict[str, Any] = {}
|
|
241
286
|
if runningTime:
|
|
242
287
|
profiling_data['runningTime'] = str(td)
|
|
243
288
|
if len(runtime_recs) > 0:
|
|
@@ -280,7 +325,7 @@ class ClamsApp(ABC):
|
|
|
280
325
|
if self._RAW_PARAMS_KEY in runtime_params:
|
|
281
326
|
# meaning the dict is already refined, just return it
|
|
282
327
|
return runtime_params
|
|
283
|
-
refined = {}
|
|
328
|
+
refined: Dict[str, Any] = {}
|
|
284
329
|
|
|
285
330
|
casted = self.annotate_param_caster.cast(runtime_params)
|
|
286
331
|
for parameter in self.metadata.parameters:
|
|
@@ -491,7 +536,7 @@ class ClamsApp(ABC):
|
|
|
491
536
|
|
|
492
537
|
# Fallback to torch (only sees current process memory)
|
|
493
538
|
try:
|
|
494
|
-
import torch
|
|
539
|
+
import torch
|
|
495
540
|
if not torch.cuda.is_available():
|
|
496
541
|
return 0
|
|
497
542
|
|
|
@@ -595,7 +640,7 @@ class ClamsApp(ABC):
|
|
|
595
640
|
available_before = {}
|
|
596
641
|
|
|
597
642
|
try:
|
|
598
|
-
import torch
|
|
643
|
+
import torch
|
|
599
644
|
torch_available = True
|
|
600
645
|
cuda_available = torch.cuda.is_available()
|
|
601
646
|
device_count = torch.cuda.device_count()
|
|
@@ -656,11 +701,9 @@ class ClamsApp(ABC):
|
|
|
656
701
|
}
|
|
657
702
|
|
|
658
703
|
# Record peak memory for future requests (if GPU app)
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
)
|
|
663
|
-
if gpu_app and total_peak > 0:
|
|
704
|
+
app_metadata = getattr(app_instance, 'metadata', None)
|
|
705
|
+
gpu_app = getattr(app_metadata, 'est_gpu_mem_min', 0) > 0
|
|
706
|
+
if app_instance is not None and gpu_app and total_peak > 0:
|
|
664
707
|
app_instance._record_vram_usage(kwargs, total_peak)
|
|
665
708
|
|
|
666
709
|
return result, cuda_profiler
|
|
@@ -729,6 +772,11 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
729
772
|
parameter set to ``AppMetadata``
|
|
730
773
|
* :py:meth:`build_conversation` : assembles a chat-template-compatible
|
|
731
774
|
message list from a prompt plus optional images/audios
|
|
775
|
+
* :py:meth:`chunk_tasks` : caps images per prompt by splitting
|
|
776
|
+
:class:`PromptTask`\\ s (``maxImagesPerPrompt``)
|
|
777
|
+
* :py:meth:`generate_batched` : runs :py:meth:`generate` in batches
|
|
778
|
+
of at most ``parallelPrompts`` prompts, reporting through
|
|
779
|
+
:py:meth:`report_progress`
|
|
732
780
|
* :py:meth:`response_to_grounded_textdocument` : persists a
|
|
733
781
|
generated response into a view as ``TextDocument`` +
|
|
734
782
|
``Alignment`` (+ optional ``origins`` / ``origination``)
|
|
@@ -737,7 +785,7 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
737
785
|
#: SDK-managed runtime parameters injected into every promptable app.
|
|
738
786
|
#: These names are reserved; apps cannot redeclare them with
|
|
739
787
|
#: customized specs.
|
|
740
|
-
promptable_parameters = [
|
|
788
|
+
promptable_parameters: List[Dict[str, Any]] = [
|
|
741
789
|
{
|
|
742
790
|
'name': 'prompt', 'type': 'string', 'multivalued': True,
|
|
743
791
|
'description':
|
|
@@ -827,6 +875,38 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
827
875
|
'Keep at ``1`` on memory-tight setups; raise only when '
|
|
828
876
|
'per-prompt content is small and bounded.',
|
|
829
877
|
},
|
|
878
|
+
{
|
|
879
|
+
'name': 'maxImagesPerPrompt', 'type': 'integer', 'default': 0,
|
|
880
|
+
'description':
|
|
881
|
+
'Maximum number of images the app bundles into a single '
|
|
882
|
+
'prompt (one generation, one output TextDocument). ``0`` '
|
|
883
|
+
'means no limit, so all images for a TimeFrame go into one '
|
|
884
|
+
'prompt and yield one TextDocument. With a positive N, the '
|
|
885
|
+
'app splits a TimeFrame whose frames exceed N into '
|
|
886
|
+
'consecutive groups of at most N; each group produces its '
|
|
887
|
+
'own TextDocument aligned to the same TimeFrame and grounded '
|
|
888
|
+
'to its own source TimePoints via ``origins``. This bounds '
|
|
889
|
+
'GPU memory for long dynamic scenes. The value is '
|
|
890
|
+
'operator-set, so the number of output TextDocuments is '
|
|
891
|
+
'deterministic and does not vary by GPU. Composes with '
|
|
892
|
+
'``parallelPrompts``, which batches independent prompts '
|
|
893
|
+
'into one forward pass.',
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
'name': 'imagesPerPromptMode', 'type': 'string',
|
|
897
|
+
'choices': [m.value for m in ImagesPerPromptMode],
|
|
898
|
+
'default': ImagesPerPromptMode.MAX.value,
|
|
899
|
+
'description':
|
|
900
|
+
'How the images of a TimeFrame that exceeds '
|
|
901
|
+
'``maxImagesPerPrompt`` are spread over its prompts. Has '
|
|
902
|
+
'no effect when ``maxImagesPerPrompt`` is ``0``. Both '
|
|
903
|
+
'choices produce the same number of prompts. "max" fills '
|
|
904
|
+
'each prompt to the cap and puts the remainder in the '
|
|
905
|
+
'last one (33 images at a cap of 32 give 32 and 1). '
|
|
906
|
+
'"balanced" spreads the images evenly, so prompt sizes '
|
|
907
|
+
'differ by at most one (33 images at a cap of 32 give 17 '
|
|
908
|
+
'and 16).',
|
|
909
|
+
},
|
|
830
910
|
]
|
|
831
911
|
|
|
832
912
|
@staticmethod
|
|
@@ -978,7 +1058,7 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
978
1058
|
if isinstance(prompt, str):
|
|
979
1059
|
prompts = [prompt]
|
|
980
1060
|
else:
|
|
981
|
-
prompts = list(prompt)
|
|
1061
|
+
prompts = cast(List[str], list(prompt))
|
|
982
1062
|
|
|
983
1063
|
if len(prompts) == 1:
|
|
984
1064
|
return self._build_single_turn(
|
|
@@ -1072,6 +1152,148 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
1072
1152
|
base.append({'role': 'assistant', 'content': None})
|
|
1073
1153
|
return convs
|
|
1074
1154
|
|
|
1155
|
+
def generate_batched(
|
|
1156
|
+
self,
|
|
1157
|
+
prompt: List[str],
|
|
1158
|
+
system_prompt: str = '',
|
|
1159
|
+
images: Optional[List[List[Any]]] = None,
|
|
1160
|
+
audios: Optional[List[List[Any]]] = None,
|
|
1161
|
+
prompt_mode: str = 'turn-taking',
|
|
1162
|
+
parallel_prompts: int = 1,
|
|
1163
|
+
**generation_params,
|
|
1164
|
+
) -> List[str]:
|
|
1165
|
+
"""
|
|
1166
|
+
Run N prompts through :py:meth:`generate` in batches of at most
|
|
1167
|
+
``parallel_prompts`` prompts per call, and return N outputs in
|
|
1168
|
+
input order. Apps typically pass the ``parallelPrompts`` runtime
|
|
1169
|
+
parameter as ``parallel_prompts``.
|
|
1170
|
+
|
|
1171
|
+
The arguments other than ``parallel_prompts`` have the same
|
|
1172
|
+
meaning as in :py:meth:`generate` and are forwarded to it. A
|
|
1173
|
+
text-only call (``images`` and ``audios`` both ``None``) is one
|
|
1174
|
+
prompt and runs as a single :py:meth:`generate` call.
|
|
1175
|
+
|
|
1176
|
+
Progress is reported through :py:meth:`report_progress`: once
|
|
1177
|
+
before the first batch and once after each batch.
|
|
1178
|
+
|
|
1179
|
+
:param parallel_prompts: maximum number of prompts per
|
|
1180
|
+
:py:meth:`generate` call.
|
|
1181
|
+
:returns: one output per prompt, in input order.
|
|
1182
|
+
:rtype: List[str]
|
|
1183
|
+
:raises ValueError: if ``parallel_prompts`` is less than 1, or if
|
|
1184
|
+
``images`` and ``audios`` are both given with different outer
|
|
1185
|
+
lengths.
|
|
1186
|
+
"""
|
|
1187
|
+
if parallel_prompts < 1:
|
|
1188
|
+
raise ValueError(
|
|
1189
|
+
f"parallel_prompts must be at least 1; "
|
|
1190
|
+
f"got {parallel_prompts}.")
|
|
1191
|
+
if images is not None and audios is not None \
|
|
1192
|
+
and len(images) != len(audios):
|
|
1193
|
+
raise ValueError(
|
|
1194
|
+
f"images and audios must have the same outer length "
|
|
1195
|
+
f"when both are given; got "
|
|
1196
|
+
f"{len(images)} vs {len(audios)}.")
|
|
1197
|
+
groups = images if images is not None else audios
|
|
1198
|
+
if groups is None:
|
|
1199
|
+
self.report_progress(0, 1)
|
|
1200
|
+
outputs = self.generate(
|
|
1201
|
+
prompt, system_prompt=system_prompt,
|
|
1202
|
+
prompt_mode=prompt_mode, **generation_params)
|
|
1203
|
+
self.report_progress(1, 1)
|
|
1204
|
+
return outputs
|
|
1205
|
+
total = len(groups)
|
|
1206
|
+
outputs = []
|
|
1207
|
+
self.report_progress(0, total)
|
|
1208
|
+
for start in range(0, total, parallel_prompts):
|
|
1209
|
+
end = start + parallel_prompts
|
|
1210
|
+
outputs.extend(self.generate(
|
|
1211
|
+
prompt, system_prompt=system_prompt,
|
|
1212
|
+
images=images[start:end] if images is not None else None,
|
|
1213
|
+
audios=audios[start:end] if audios is not None else None,
|
|
1214
|
+
prompt_mode=prompt_mode, **generation_params))
|
|
1215
|
+
self.report_progress(min(end, total), total)
|
|
1216
|
+
return outputs
|
|
1217
|
+
|
|
1218
|
+
def report_progress(self, done: int, total: int) -> None:
|
|
1219
|
+
"""
|
|
1220
|
+
Receive generation progress from :py:meth:`generate_batched`,
|
|
1221
|
+
which calls this once with ``done=0`` before the first batch and
|
|
1222
|
+
once after each batch. The default implementation writes one
|
|
1223
|
+
INFO log line. Override to report differently (another log
|
|
1224
|
+
level or wording, a progress bar) or to stay silent.
|
|
1225
|
+
|
|
1226
|
+
The app instance serves all requests, so an override that keeps
|
|
1227
|
+
state on ``self`` between calls is shared by concurrent
|
|
1228
|
+
requests.
|
|
1229
|
+
|
|
1230
|
+
:param done: number of prompts generated so far.
|
|
1231
|
+
:param total: number of prompts in this
|
|
1232
|
+
:py:meth:`generate_batched` call.
|
|
1233
|
+
"""
|
|
1234
|
+
self.logger.info(f"Generated {done}/{total} prompts")
|
|
1235
|
+
|
|
1236
|
+
@staticmethod
|
|
1237
|
+
def chunk_tasks(
|
|
1238
|
+
tasks: List[PromptTask],
|
|
1239
|
+
max_images: int,
|
|
1240
|
+
mode: Union[str, ImagesPerPromptMode] = ImagesPerPromptMode.MAX,
|
|
1241
|
+
) -> List[PromptTask]:
|
|
1242
|
+
"""
|
|
1243
|
+
Split each task with more than ``max_images`` images into
|
|
1244
|
+
consecutive tasks of at most ``max_images`` images, chunking
|
|
1245
|
+
``origins`` in step and keeping ``source``. Tasks at or under the
|
|
1246
|
+
cap pass through unchanged, and task order is preserved. Apps
|
|
1247
|
+
typically pass the ``maxImagesPerPrompt`` and
|
|
1248
|
+
``imagesPerPromptMode`` runtime parameters as ``max_images`` and
|
|
1249
|
+
``mode``.
|
|
1250
|
+
|
|
1251
|
+
Each resulting task yields one response and one ``TextDocument``,
|
|
1252
|
+
so an over-cap TimeFrame produces several ``TextDocument``\\ s
|
|
1253
|
+
aligned to the same ``source``, each grounded to its own
|
|
1254
|
+
``origins``.
|
|
1255
|
+
|
|
1256
|
+
:param tasks: the tasks to split.
|
|
1257
|
+
:param max_images: maximum number of images per task. ``0`` means
|
|
1258
|
+
no cap.
|
|
1259
|
+
:param mode: how the images of an over-cap task are spread over
|
|
1260
|
+
its chunks; see :class:`ImagesPerPromptMode`.
|
|
1261
|
+
:returns: the capped tasks, in order.
|
|
1262
|
+
:rtype: List[PromptTask]
|
|
1263
|
+
:raises ValueError: if ``max_images`` is negative or ``mode`` is
|
|
1264
|
+
not a valid :class:`ImagesPerPromptMode`.
|
|
1265
|
+
"""
|
|
1266
|
+
mode = ImagesPerPromptMode(mode)
|
|
1267
|
+
if max_images < 0:
|
|
1268
|
+
raise ValueError(
|
|
1269
|
+
f"max_images must be 0 (no cap) or positive; "
|
|
1270
|
+
f"got {max_images}.")
|
|
1271
|
+
if max_images == 0:
|
|
1272
|
+
return list(tasks)
|
|
1273
|
+
capped: List[PromptTask] = []
|
|
1274
|
+
for task in tasks:
|
|
1275
|
+
n_images = len(task.images)
|
|
1276
|
+
if n_images <= max_images:
|
|
1277
|
+
capped.append(task)
|
|
1278
|
+
continue
|
|
1279
|
+
n_chunks = -(-n_images // max_images)
|
|
1280
|
+
if mode is ImagesPerPromptMode.BALANCED:
|
|
1281
|
+
size, n_larger = divmod(n_images, n_chunks)
|
|
1282
|
+
sizes = ([size + 1] * n_larger
|
|
1283
|
+
+ [size] * (n_chunks - n_larger))
|
|
1284
|
+
else:
|
|
1285
|
+
sizes = [max_images] * (n_chunks - 1)
|
|
1286
|
+
sizes.append(n_images - sum(sizes))
|
|
1287
|
+
start = 0
|
|
1288
|
+
for size in sizes:
|
|
1289
|
+
end = start + size
|
|
1290
|
+
capped.append(PromptTask(
|
|
1291
|
+
images=task.images[start:end],
|
|
1292
|
+
origins=task.origins[start:end],
|
|
1293
|
+
source=task.source))
|
|
1294
|
+
start = end
|
|
1295
|
+
return capped
|
|
1296
|
+
|
|
1075
1297
|
def response_to_grounded_textdocument(
|
|
1076
1298
|
self,
|
|
1077
1299
|
view: View,
|
|
@@ -1330,7 +1552,8 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1330
1552
|
"non-empty dict (HF model id -> commit hash). Set "
|
|
1331
1553
|
"it on the ``AppMetadata`` constructor call before "
|
|
1332
1554
|
"invoking this helper.")
|
|
1333
|
-
choices = list(
|
|
1555
|
+
choices: List[real_valued_primitives] = list(
|
|
1556
|
+
analyzer_versions.keys())
|
|
1334
1557
|
default = choices[0] if len(choices) == 1 else None
|
|
1335
1558
|
metadata.add_parameter(
|
|
1336
1559
|
name='model',
|
|
@@ -1438,7 +1661,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1438
1661
|
model_id, _, revision = model_id_or_with_rev.rpartition('@')
|
|
1439
1662
|
else:
|
|
1440
1663
|
model_id = model_id_or_with_rev
|
|
1441
|
-
revision = self.metadata.analyzer_versions[model_id]
|
|
1664
|
+
revision = (self.metadata.analyzer_versions or {})[model_id]
|
|
1442
1665
|
cache_key = (model_id, revision)
|
|
1443
1666
|
cached = self._model_cache.get(cache_key)
|
|
1444
1667
|
if cached is not None:
|
|
@@ -1590,7 +1813,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1590
1813
|
ignores them, so subclasses can pass through the full
|
|
1591
1814
|
``**parameters`` dict from ``_annotate`` without filtering.
|
|
1592
1815
|
"""
|
|
1593
|
-
gen_kwargs = {'max_new_tokens': max_new_tokens}
|
|
1816
|
+
gen_kwargs: Dict[str, Any] = {'max_new_tokens': max_new_tokens}
|
|
1594
1817
|
if temperature > 0:
|
|
1595
1818
|
gen_kwargs.update({
|
|
1596
1819
|
'do_sample': True,
|
|
@@ -1628,7 +1851,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1628
1851
|
|
|
1629
1852
|
class ParameterCaster(object):
|
|
1630
1853
|
|
|
1631
|
-
def __init__(self, param_spec:
|
|
1854
|
+
def __init__(self, param_spec: Mapping[str, Tuple[str, bool]]):
|
|
1632
1855
|
"""
|
|
1633
1856
|
A helper class to convert parameters passed by HTTP query strings to
|
|
1634
1857
|
proper python data types.
|
|
@@ -1657,7 +1880,7 @@ class ParameterCaster(object):
|
|
|
1657
1880
|
With the third case, developers can further process the nested values into a more complex data types or
|
|
1658
1881
|
structures, but that is not in the scope of this Caster class.
|
|
1659
1882
|
"""
|
|
1660
|
-
casted = {}
|
|
1883
|
+
casted: Dict[str, Any] = {}
|
|
1661
1884
|
for k, vs in args.items():
|
|
1662
1885
|
assert isinstance(vs, list), f"Expected a list of values for key {k}, but got {vs} of type {type(vs)}"
|
|
1663
1886
|
assert all(isinstance(v, str) for v in vs), f"Expected a list of strings for key {k}, but got {vs} of types {[type(v) for v in vs]}"
|
|
@@ -1665,29 +1888,24 @@ class ParameterCaster(object):
|
|
|
1665
1888
|
valuetype, multivalued = self.param_spec[k]
|
|
1666
1889
|
for v in vs:
|
|
1667
1890
|
if multivalued or k not in casted: # effectively only keeps the first value for non-multi params
|
|
1891
|
+
value: Any = v
|
|
1668
1892
|
if valuetype == bool:
|
|
1669
|
-
|
|
1893
|
+
value = self.bool_param(v)
|
|
1670
1894
|
elif valuetype == float:
|
|
1671
|
-
|
|
1895
|
+
value = self.float_param(v)
|
|
1672
1896
|
elif valuetype == int:
|
|
1673
|
-
|
|
1897
|
+
value = self.int_param(v)
|
|
1674
1898
|
elif valuetype == str:
|
|
1675
|
-
|
|
1899
|
+
value = self.str_param(v)
|
|
1676
1900
|
elif valuetype == dict:
|
|
1677
|
-
|
|
1901
|
+
value = self.kv_param(v)
|
|
1678
1902
|
if multivalued:
|
|
1679
1903
|
if valuetype == dict:
|
|
1680
|
-
casted.setdefault(k, {}).update(
|
|
1904
|
+
casted.setdefault(k, {}).update(value)
|
|
1681
1905
|
else:
|
|
1682
|
-
|
|
1683
|
-
# casted.setdefault(k, []).append(v)
|
|
1684
|
-
# so doing it in a more explicit way
|
|
1685
|
-
if k in casted and isinstance(casted[k], list):
|
|
1686
|
-
casted[k].append(v)
|
|
1687
|
-
else:
|
|
1688
|
-
casted[k] = [v]
|
|
1906
|
+
casted.setdefault(k, []).append(value)
|
|
1689
1907
|
else:
|
|
1690
|
-
casted[k] =
|
|
1908
|
+
casted[k] = value
|
|
1691
1909
|
# when an empty value is passed (usually as a default value)
|
|
1692
1910
|
# just add an empty list or dict as a placeholder
|
|
1693
1911
|
# explicit check for `len == 0` is required to prevent
|
|
@@ -1704,7 +1922,7 @@ class ParameterCaster(object):
|
|
|
1704
1922
|
casted[k] = vs[0]
|
|
1705
1923
|
else:
|
|
1706
1924
|
casted[k] = vs
|
|
1707
|
-
return casted
|
|
1925
|
+
return casted
|
|
1708
1926
|
|
|
1709
1927
|
@staticmethod
|
|
1710
1928
|
def bool_param(value) -> bool:
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import inspect
|
|
1
2
|
import json
|
|
2
3
|
import os
|
|
3
4
|
import shutil
|
|
4
5
|
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Union, Dict, List, Optional, Literal, Any
|
|
8
|
+
from typing import Union, Dict, List, Optional, Literal, Any, get_args
|
|
8
9
|
|
|
9
10
|
import mmif
|
|
10
11
|
import pydantic
|
|
@@ -22,7 +23,7 @@ map_param_kv_delimiter = ':'
|
|
|
22
23
|
# dict to map app parameter data types to Python data types
|
|
23
24
|
python_type = {"boolean": bool, "number": float, "integer": int, "string": str, "map": dict}
|
|
24
25
|
|
|
25
|
-
param_value_types_values = param_value_types
|
|
26
|
+
param_value_types_values = get_args(param_value_types)
|
|
26
27
|
app_directory_baseurl = "http://apps.clams.ai"
|
|
27
28
|
|
|
28
29
|
|
|
@@ -42,7 +43,9 @@ def get_clams_pyver():
|
|
|
42
43
|
|
|
43
44
|
def generate_app_version(cwd=None):
|
|
44
45
|
gitcmd = shutil.which('git')
|
|
45
|
-
|
|
46
|
+
if cwd is None:
|
|
47
|
+
cwd = Path(inspect.getfile(sys.modules["__main__"])).parent.resolve()
|
|
48
|
+
gitdir = Path(cwd) / '.git'
|
|
46
49
|
if gitcmd is not None and gitdir.exists():
|
|
47
50
|
try:
|
|
48
51
|
proc = subprocess.run([gitcmd, '--git-dir', str(gitdir), 'describe', '--tags', '--always'],
|
|
@@ -60,6 +63,12 @@ def get_mmif_specver():
|
|
|
60
63
|
return mmif.__specver__
|
|
61
64
|
|
|
62
65
|
|
|
66
|
+
# TODO (krim @ 10/05/26): in the 2.0.0 cycle, make the pydantic-specific
|
|
67
|
+
# helpers in this module private (underscore-prefixed), as they are not
|
|
68
|
+
# part of the developer-facing API: the schema adjusters (`pop_titles`,
|
|
69
|
+
# `jsonschema_versioning`) and the validators (`validate_properties`,
|
|
70
|
+
# `at_type_must_be_str`, `assign_versions`, `validate_gpu_memory`,
|
|
71
|
+
# `append_version`).
|
|
63
72
|
def pop_titles(js):
|
|
64
73
|
for prop in js.get('properties', {}).values():
|
|
65
74
|
prop.pop('title', None)
|
|
@@ -70,6 +79,11 @@ def jsonschema_versioning(js):
|
|
|
70
79
|
js['$comment'] = f"clams-python SDK {get_clams_pyver()} was used to generate this schema"
|
|
71
80
|
|
|
72
81
|
|
|
82
|
+
def _adjust_appmetadata_schema(js):
|
|
83
|
+
pop_titles(js)
|
|
84
|
+
jsonschema_versioning(js)
|
|
85
|
+
|
|
86
|
+
|
|
73
87
|
class _BaseModel(pydantic.BaseModel):
|
|
74
88
|
|
|
75
89
|
model_config = {
|
|
@@ -381,7 +395,7 @@ class AppMetadata(pydantic.BaseModel):
|
|
|
381
395
|
'title': 'CLAMS AppMetadata',
|
|
382
396
|
'extra': 'forbid',
|
|
383
397
|
'validate_by_name': True,
|
|
384
|
-
'json_schema_extra':
|
|
398
|
+
'json_schema_extra': _adjust_appmetadata_schema,
|
|
385
399
|
}
|
|
386
400
|
|
|
387
401
|
@pydantic.model_validator(mode='after')
|