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.
Files changed (29) hide show
  1. jsonprofile-0.1.1/PKG-INFO +33 -0
  2. jsonprofile-0.1.1/README.md +13 -0
  3. jsonprofile-0.1.1/pyproject.toml +82 -0
  4. jsonprofile-0.1.1/pyproject.toml.orig +73 -0
  5. jsonprofile-0.1.1/src/jsonprofile/__init__.py +6 -0
  6. jsonprofile-0.1.1/src/jsonprofile/cv_term_search.py +159 -0
  7. jsonprofile-0.1.1/src/jsonprofile/jsonprofile.schema.json +5352 -0
  8. jsonprofile-0.1.1/src/jsonprofile/profile/__init__.py +47 -0
  9. jsonprofile-0.1.1/src/jsonprofile/profile/base.py +347 -0
  10. jsonprofile-0.1.1/src/jsonprofile/profile/constraints/__init__.py +63 -0
  11. jsonprofile-0.1.1/src/jsonprofile/profile/constraints/constraints.py +851 -0
  12. jsonprofile-0.1.1/src/jsonprofile/profile/model.py +360 -0
  13. jsonprofile-0.1.1/src/jsonprofile/profile/profile_validator.py +204 -0
  14. jsonprofile-0.1.1/src/jsonprofile/utils.py +98 -0
  15. jsonprofile-0.1.1/src/jsonprofile/validator/__init__.py +43 -0
  16. jsonprofile-0.1.1/src/jsonprofile/validator/abstract_checker.py +248 -0
  17. jsonprofile-0.1.1/src/jsonprofile/validator/base.py +145 -0
  18. jsonprofile-0.1.1/src/jsonprofile/validator/checkers/__init__.py +59 -0
  19. jsonprofile-0.1.1/src/jsonprofile/validator/checkers/custom_checker.py +84 -0
  20. jsonprofile-0.1.1/src/jsonprofile/validator/checkers/default_checker.py +1506 -0
  21. jsonprofile-0.1.1/src/jsonprofile/validator/context.py +126 -0
  22. jsonprofile-0.1.1/src/jsonprofile/validator/decorators.py +57 -0
  23. jsonprofile-0.1.1/src/jsonprofile/validator/default/__init__.py +13 -0
  24. jsonprofile-0.1.1/src/jsonprofile/validator/default/ols_cv_term_search.py +410 -0
  25. jsonprofile-0.1.1/src/jsonprofile/validator/default/profile_validator.py +144 -0
  26. jsonprofile-0.1.1/src/jsonprofile/validator/default/profile_validator_factory.py +284 -0
  27. jsonprofile-0.1.1/src/jsonprofile/validator/json_validator.py +978 -0
  28. jsonprofile-0.1.1/src/jsonprofile/validator/local_opa_engine.py +461 -0
  29. 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,6 @@
1
+ from jsonprofile import profile, utils, validator
2
+
3
+ __version__ = "v0.1.1"
4
+
5
+
6
+ __all__ = ["profile", "validator", "utils", "cv_term_search"]
@@ -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