jsonprofile 0.1.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.
- jsonprofile-0.1.1/PKG-INFO +33 -0
- jsonprofile-0.1.1/README.md +13 -0
- jsonprofile-0.1.1/pyproject.toml +82 -0
- jsonprofile-0.1.1/pyproject.toml.orig +73 -0
- jsonprofile-0.1.1/src/jsonprofile/__init__.py +6 -0
- jsonprofile-0.1.1/src/jsonprofile/cv_term_search.py +159 -0
- jsonprofile-0.1.1/src/jsonprofile/jsonprofile.schema.json +5352 -0
- jsonprofile-0.1.1/src/jsonprofile/profile/__init__.py +47 -0
- jsonprofile-0.1.1/src/jsonprofile/profile/base.py +347 -0
- jsonprofile-0.1.1/src/jsonprofile/profile/constraints/__init__.py +63 -0
- jsonprofile-0.1.1/src/jsonprofile/profile/constraints/constraints.py +851 -0
- jsonprofile-0.1.1/src/jsonprofile/profile/model.py +360 -0
- jsonprofile-0.1.1/src/jsonprofile/profile/profile_validator.py +204 -0
- jsonprofile-0.1.1/src/jsonprofile/utils.py +98 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/__init__.py +43 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/abstract_checker.py +248 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/base.py +145 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/checkers/__init__.py +59 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/checkers/custom_checker.py +84 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/checkers/default_checker.py +1506 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/context.py +126 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/decorators.py +57 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/default/__init__.py +13 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/default/ols_cv_term_search.py +410 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/default/profile_validator.py +144 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/default/profile_validator_factory.py +284 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/json_validator.py +978 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/local_opa_engine.py +461 -0
- jsonprofile-0.1.1/src/jsonprofile/validator/opa_engine.py +55 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: jsonprofile
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Dynamic profile management for json files share same schema
|
|
5
|
+
Requires-Dist: bioregistry>=0.13.64
|
|
6
|
+
Requires-Dist: cachetools>=7.1.4
|
|
7
|
+
Requires-Dist: email-validator>=2.3.0
|
|
8
|
+
Requires-Dist: httpx2>=0.28.1
|
|
9
|
+
Requires-Dist: jsonpath-ng>=1.8.0
|
|
10
|
+
Requires-Dist: jsonschema-rs>=0.46.9
|
|
11
|
+
Requires-Dist: ontology-lookup>=0.1.1
|
|
12
|
+
Requires-Dist: orjson>=3.11.9
|
|
13
|
+
Requires-Dist: pydantic>=2.12.4
|
|
14
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
15
|
+
Requires-Dist: rfc3986>=2.0.0
|
|
16
|
+
Requires-Dist: opa-wasmtime>=0.1.1 ; extra == 'opa'
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Provides-Extra: opa
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# jsonprofile Python
|
|
22
|
+
|
|
23
|
+
Python implementation of jsonprofile.
|
|
24
|
+
|
|
25
|
+
Requires Python 3.10 or later. CI tests Python 3.10 through 3.14.
|
|
26
|
+
|
|
27
|
+
From this directory, run `uv sync --locked` to install dependencies and
|
|
28
|
+
`uv run pytest` to run the tests. The default development interpreter is
|
|
29
|
+
Python 3.10; use `uv run --python 3.14 pytest` to test another version.
|
|
30
|
+
|
|
31
|
+
Local OPA policy evaluation is optional. Install it with `pip install "jsonprofile[opa]"`
|
|
32
|
+
or use `uv sync --extra opa` when working from this directory. The validator
|
|
33
|
+
automatically uses the local OPA engine when the optional dependency is installed.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# jsonprofile Python
|
|
2
|
+
|
|
3
|
+
Python implementation of jsonprofile.
|
|
4
|
+
|
|
5
|
+
Requires Python 3.10 or later. CI tests Python 3.10 through 3.14.
|
|
6
|
+
|
|
7
|
+
From this directory, run `uv sync --locked` to install dependencies and
|
|
8
|
+
`uv run pytest` to run the tests. The default development interpreter is
|
|
9
|
+
Python 3.10; use `uv run --python 3.14 pytest` to test another version.
|
|
10
|
+
|
|
11
|
+
Local OPA policy evaluation is optional. Install it with `pip install "jsonprofile[opa]"`
|
|
12
|
+
or use `uv sync --extra opa` when working from this directory. The validator
|
|
13
|
+
automatically uses the local OPA engine when the optional dependency is installed.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "jsonprofile"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Dynamic profile management for json files share same schema"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"bioregistry>=0.13.64",
|
|
9
|
+
"cachetools>=7.1.4",
|
|
10
|
+
"email-validator>=2.3.0",
|
|
11
|
+
"httpx2>=0.28.1",
|
|
12
|
+
"jsonpath-ng>=1.8.0",
|
|
13
|
+
"jsonschema-rs>=0.46.9",
|
|
14
|
+
"ontology-lookup>=0.1.1",
|
|
15
|
+
"orjson>=3.11.9",
|
|
16
|
+
"pydantic>=2.12.4",
|
|
17
|
+
"pyyaml>=6.0.3",
|
|
18
|
+
"rfc3986>=2.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
opa = ["opa-wasmtime>=0.1.1"]
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pre-commit>=3.10.1",
|
|
27
|
+
"commitizen>=4.13.9",
|
|
28
|
+
"pytest>=9.0.3",
|
|
29
|
+
"ruff>=0.17.00",
|
|
30
|
+
"uv-build>=0.9.8",
|
|
31
|
+
"mkdocs-material>=9.7.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
target-version = "py310"
|
|
36
|
+
cache-dir = "../.cache/python/ruff"
|
|
37
|
+
src = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff.lint]
|
|
40
|
+
select = [
|
|
41
|
+
"E",
|
|
42
|
+
"F",
|
|
43
|
+
"C",
|
|
44
|
+
"I",
|
|
45
|
+
"G",
|
|
46
|
+
"SLF",
|
|
47
|
+
"PTH",
|
|
48
|
+
"Q",
|
|
49
|
+
"T",
|
|
50
|
+
]
|
|
51
|
+
ignore = ["C901"]
|
|
52
|
+
fixable = ["ALL"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint.isort]
|
|
55
|
+
known-first-party = ["jsonprofile"]
|
|
56
|
+
|
|
57
|
+
[tool.commitizen]
|
|
58
|
+
name = "cz_conventional_commits"
|
|
59
|
+
version_provider = "uv"
|
|
60
|
+
tag_format = "v$major.$minor.$patch"
|
|
61
|
+
version_files = [
|
|
62
|
+
"pyproject.toml:version",
|
|
63
|
+
"src/jsonprofile/__init__.py:__version__",
|
|
64
|
+
]
|
|
65
|
+
bump_message = "bump: version $current_version -> $new_version"
|
|
66
|
+
update_changelog_on_bump = true
|
|
67
|
+
|
|
68
|
+
[tool.pytest.ini_options]
|
|
69
|
+
addopts = "-v"
|
|
70
|
+
cache_dir = "../.cache/python/pytest"
|
|
71
|
+
testpaths = ["tests"]
|
|
72
|
+
log_cli = true
|
|
73
|
+
log_cli_level = "INFO"
|
|
74
|
+
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
|
75
|
+
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
|
|
76
|
+
|
|
77
|
+
[tool.uv.build-backend]
|
|
78
|
+
module-name = "jsonprofile"
|
|
79
|
+
|
|
80
|
+
[build-system]
|
|
81
|
+
requires = ["uv_build>=0.9.8,<0.12.0"]
|
|
82
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "jsonprofile"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Dynamic profile management for json files share same schema"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"bioregistry>=0.13.64",
|
|
9
|
+
"cachetools>=7.1.4",
|
|
10
|
+
"email-validator>=2.3.0",
|
|
11
|
+
"httpx2>=0.28.1",
|
|
12
|
+
"jsonpath-ng>=1.8.0",
|
|
13
|
+
# "jsonschema>=4.26.0",
|
|
14
|
+
"jsonschema-rs>=0.46.9",
|
|
15
|
+
"ontology-lookup>=0.1.1",
|
|
16
|
+
"orjson>=3.11.9",
|
|
17
|
+
"pydantic>=2.12.4",
|
|
18
|
+
"pyyaml>=6.0.3",
|
|
19
|
+
"rfc3986>=2.0.0",
|
|
20
|
+
]
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
opa = ["opa-wasmtime>=0.1.1"]
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pre-commit>=3.10.1",
|
|
27
|
+
"commitizen>=4.13.9",
|
|
28
|
+
"pytest>=9.0.3",
|
|
29
|
+
"ruff>=0.17.00",
|
|
30
|
+
"uv-build>=0.9.8",
|
|
31
|
+
"mkdocs-material>=9.7.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
target-version = "py310"
|
|
36
|
+
cache-dir = "../.cache/python/ruff"
|
|
37
|
+
src = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.commitizen]
|
|
40
|
+
name = "cz_conventional_commits"
|
|
41
|
+
version_provider = "uv"
|
|
42
|
+
tag_format = "v$major.$minor.$patch"
|
|
43
|
+
version_files = [
|
|
44
|
+
"pyproject.toml:version",
|
|
45
|
+
"src/jsonprofile/__init__.py:__version__",
|
|
46
|
+
]
|
|
47
|
+
bump_message = "bump: version $current_version -> $new_version"
|
|
48
|
+
update_changelog_on_bump = true
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = ["E", "F", "C", "I", "G", "SLF", "PTH", "Q", "T"]
|
|
52
|
+
ignore = [ "C901"]
|
|
53
|
+
|
|
54
|
+
fixable = ["ALL"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint.isort]
|
|
57
|
+
known-first-party = ["jsonprofile"]
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
addopts = "-v"
|
|
61
|
+
cache_dir = "../.cache/python/pytest"
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
log_cli = true
|
|
64
|
+
log_cli_level = "INFO"
|
|
65
|
+
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
|
66
|
+
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
|
|
67
|
+
|
|
68
|
+
[build-system]
|
|
69
|
+
requires = ["uv_build>=0.9.8,<0.12.0"]
|
|
70
|
+
build-backend = "uv_build"
|
|
71
|
+
|
|
72
|
+
[tool.uv.build-backend]
|
|
73
|
+
module-name = "jsonprofile"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from ontology_lookup import OntologyLookupService
|
|
4
|
+
from ontology_lookup.models import (
|
|
5
|
+
CurieResolutionResponse,
|
|
6
|
+
IriResolutionResponse,
|
|
7
|
+
SearchTermSummary,
|
|
8
|
+
TermResponse,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from jsonprofile.profile.base import CvTerm as ProfileCvTerm
|
|
12
|
+
from jsonprofile.validator.base import (
|
|
13
|
+
CvTermSearch,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OntologyLookupCvTermSearch(CvTermSearch):
|
|
20
|
+
def __init__(self, db_path: str, *args, **kwargs):
|
|
21
|
+
self.svc = OntologyLookupService(db_path=db_path)
|
|
22
|
+
|
|
23
|
+
def get_iri(self, cv_term: ProfileCvTerm) -> str | None:
|
|
24
|
+
if cv_term.cv_accession.startswith(("http://", "https://")):
|
|
25
|
+
return cv_term.cv_accession
|
|
26
|
+
result: None | IriResolutionResponse = self.svc.find_iri(
|
|
27
|
+
curie=cv_term.cv_accession, ontology=cv_term.cv_label
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if not result:
|
|
31
|
+
return None
|
|
32
|
+
return result.iri or None
|
|
33
|
+
|
|
34
|
+
def get_curie(self, cv_term: ProfileCvTerm) -> str | None:
|
|
35
|
+
if not cv_term.cv_accession.startswith(("http://", "https://")):
|
|
36
|
+
return cv_term.cv_accession
|
|
37
|
+
result: CurieResolutionResponse = self.svc.find_curie(
|
|
38
|
+
iri=cv_term.cv_accession, ontology=cv_term.cv_label
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if not result:
|
|
42
|
+
return None
|
|
43
|
+
return result.curie or None
|
|
44
|
+
|
|
45
|
+
def find_cv_term(
|
|
46
|
+
self,
|
|
47
|
+
source: str,
|
|
48
|
+
accession_or_label: str,
|
|
49
|
+
matched_accession: None | str = None,
|
|
50
|
+
allow_synonym_search: bool = False,
|
|
51
|
+
) -> None | ProfileCvTerm:
|
|
52
|
+
if matched_accession:
|
|
53
|
+
result: TermResponse = self.svc.get_term_by_accession(
|
|
54
|
+
ontology=source, accession=matched_accession
|
|
55
|
+
)
|
|
56
|
+
if result:
|
|
57
|
+
return ProfileCvTerm(
|
|
58
|
+
cv_label=result.ontology, cv_accession=result.curie, name=result.label
|
|
59
|
+
)
|
|
60
|
+
result: TermResponse = self.svc.get_term_by_accession(
|
|
61
|
+
ontology=source, accession=accession_or_label
|
|
62
|
+
)
|
|
63
|
+
if result:
|
|
64
|
+
return ProfileCvTerm(
|
|
65
|
+
cv_label=result.ontology, cv_accession=result.curie, name=result.label
|
|
66
|
+
)
|
|
67
|
+
if allow_synonym_search:
|
|
68
|
+
result: list[SearchTermSummary] = self.svc.search_by_label(
|
|
69
|
+
ontology=source, label_or_synonym=accession_or_label
|
|
70
|
+
)
|
|
71
|
+
if result:
|
|
72
|
+
result = result[0]
|
|
73
|
+
else:
|
|
74
|
+
result: TermResponse = self.svc.get_term_by_exact_label(
|
|
75
|
+
ontology=source, label=accession_or_label
|
|
76
|
+
)
|
|
77
|
+
if result:
|
|
78
|
+
return ProfileCvTerm(
|
|
79
|
+
cv_label=result.ontology, cv_accession=result.curie, name=result.label
|
|
80
|
+
)
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def check_cv_term(
|
|
84
|
+
self,
|
|
85
|
+
cv_term: ProfileCvTerm,
|
|
86
|
+
parent_cv_term: None | ProfileCvTerm = None,
|
|
87
|
+
allow_synonym_search: bool = False,
|
|
88
|
+
) -> tuple[bool, str]:
|
|
89
|
+
ontology = cv_term.cv_label
|
|
90
|
+
if not cv_term.cv_label:
|
|
91
|
+
if ":" in cv_term.cv_accession:
|
|
92
|
+
ontology = cv_term.cv_label.split(":", maxsplit=1)[0]
|
|
93
|
+
else:
|
|
94
|
+
return (
|
|
95
|
+
False,
|
|
96
|
+
f"Invalid ontology for {cv_term.name}",
|
|
97
|
+
)
|
|
98
|
+
if parent_cv_term:
|
|
99
|
+
result = self.svc.search_by_label(
|
|
100
|
+
ontology=ontology,
|
|
101
|
+
label_or_synonym=cv_term.name,
|
|
102
|
+
parent_curie=parent_cv_term.cv_accession,
|
|
103
|
+
search_in_synonyms=allow_synonym_search,
|
|
104
|
+
)
|
|
105
|
+
if result:
|
|
106
|
+
result_item = result[0]
|
|
107
|
+
if result_item.curie == cv_term.cv_accession:
|
|
108
|
+
return (
|
|
109
|
+
True,
|
|
110
|
+
f"{cv_term.cv_accession} is "
|
|
111
|
+
f"child of {parent_cv_term.cv_accession}",
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
return (
|
|
115
|
+
False,
|
|
116
|
+
f"{cv_term.cv_accession} {cv_term.name} does not match.",
|
|
117
|
+
)
|
|
118
|
+
return (
|
|
119
|
+
False,
|
|
120
|
+
f"{cv_term.cv_accession} is not child of {parent_cv_term.cv_accession}",
|
|
121
|
+
)
|
|
122
|
+
result: list[SearchTermSummary] = self.svc.search_by_label(
|
|
123
|
+
ontology=ontology,
|
|
124
|
+
label_or_synonym=cv_term.name,
|
|
125
|
+
search_in_synonyms=allow_synonym_search,
|
|
126
|
+
limit=1,
|
|
127
|
+
)
|
|
128
|
+
if result:
|
|
129
|
+
return (
|
|
130
|
+
True,
|
|
131
|
+
f"{cv_term.cv_accession} is found result with label {result[0].label} ",
|
|
132
|
+
)
|
|
133
|
+
return (
|
|
134
|
+
False,
|
|
135
|
+
f"{cv_term.cv_accession} '{cv_term.name}' "
|
|
136
|
+
f"is not found in ontology {cv_term.cv_label}",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def find_cv_term_with_accession(
|
|
140
|
+
self, source: str, accession: str
|
|
141
|
+
) -> tuple[ProfileCvTerm | None, list[str] | None]:
|
|
142
|
+
ontology = source
|
|
143
|
+
if not ontology:
|
|
144
|
+
if ":" in accession:
|
|
145
|
+
ontology = accession.split(":", maxsplit=1)[0]
|
|
146
|
+
else:
|
|
147
|
+
return None, []
|
|
148
|
+
|
|
149
|
+
result: None | TermResponse = self.svc.get_term_by_accession(
|
|
150
|
+
ontology=ontology, accession=accession
|
|
151
|
+
)
|
|
152
|
+
if result:
|
|
153
|
+
profile_cv_term = ProfileCvTerm(
|
|
154
|
+
cv_label=result.ontology,
|
|
155
|
+
cv_accession=result.curie,
|
|
156
|
+
name=result.label,
|
|
157
|
+
)
|
|
158
|
+
return profile_cv_term, result.synonyms or []
|
|
159
|
+
return None, None
|