jsonprofile 0.2.0__tar.gz → 0.2.2__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.2.0 → jsonprofile-0.2.2}/PKG-INFO +8 -7
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/pyproject.toml +14 -11
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/pyproject.toml.orig +14 -12
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/__init__.py +1 -1
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/jsonprofile.schema.json +52 -1
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/profile/base.py +1 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/profile/constraints/constraints.py +62 -7
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/abstract_checker.py +23 -5
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/checkers/default_checker.py +80 -70
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/default/ols_cv_term_search.py +10 -7
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/json_validator.py +21 -13
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/local_opa_engine.py +43 -39
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/README.md +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/cv_term_search.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/profile/__init__.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/profile/constraints/__init__.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/profile/model.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/profile/profile_validator.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/utils.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/__init__.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/base.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/checkers/__init__.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/checkers/custom_checker.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/context.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/decorators.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/default/__init__.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/default/profile_validator.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/default/profile_validator_factory.py +0 -0
- {jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/opa_engine.py +0 -0
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: jsonprofile
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Dynamic profile management for json files share same schema
|
|
5
|
-
Requires-Dist: bioregistry>=0.13.
|
|
6
|
-
Requires-Dist: cachetools>=7.
|
|
5
|
+
Requires-Dist: bioregistry>=0.13.67
|
|
6
|
+
Requires-Dist: cachetools>=7.2.8
|
|
7
7
|
Requires-Dist: email-validator>=2.3.0
|
|
8
|
-
Requires-Dist: httpx2>=
|
|
8
|
+
Requires-Dist: httpx2>=2.14.0
|
|
9
9
|
Requires-Dist: jsonpath-ng>=1.8.0
|
|
10
|
-
Requires-Dist: jsonschema-rs>=0.
|
|
10
|
+
Requires-Dist: jsonschema-rs>=0.60.3
|
|
11
11
|
Requires-Dist: ontology-lookup>=0.1.1
|
|
12
|
-
Requires-Dist: orjson>=3.
|
|
13
|
-
Requires-Dist: pydantic>=2.
|
|
12
|
+
Requires-Dist: orjson>=3.13.0
|
|
13
|
+
Requires-Dist: pydantic>=2.14.0
|
|
14
14
|
Requires-Dist: pyyaml>=6.0.3
|
|
15
15
|
Requires-Dist: rfc3986>=2.0.0
|
|
16
16
|
Requires-Dist: opa-wasmtime>=0.1.1 ; extra == 'opa'
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
|
+
Project-URL: Repository, https://github.com/MetabolomicsHub/jsonprofile
|
|
18
19
|
Provides-Extra: opa
|
|
19
20
|
Description-Content-Type: text/markdown
|
|
20
21
|
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "jsonprofile"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Dynamic profile management for json files share same schema"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"bioregistry>=0.13.
|
|
9
|
-
"cachetools>=7.
|
|
8
|
+
"bioregistry>=0.13.67",
|
|
9
|
+
"cachetools>=7.2.8",
|
|
10
10
|
"email-validator>=2.3.0",
|
|
11
|
-
"httpx2>=
|
|
11
|
+
"httpx2>=2.14.0",
|
|
12
12
|
"jsonpath-ng>=1.8.0",
|
|
13
|
-
"jsonschema-rs>=0.
|
|
13
|
+
"jsonschema-rs>=0.60.3",
|
|
14
14
|
"ontology-lookup>=0.1.1",
|
|
15
|
-
"orjson>=3.
|
|
16
|
-
"pydantic>=2.
|
|
15
|
+
"orjson>=3.13.0",
|
|
16
|
+
"pydantic>=2.14.0",
|
|
17
17
|
"pyyaml>=6.0.3",
|
|
18
18
|
"rfc3986>=2.0.0",
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
+
[project.urls]
|
|
22
|
+
Repository = "https://github.com/MetabolomicsHub/jsonprofile"
|
|
23
|
+
|
|
21
24
|
[project.optional-dependencies]
|
|
22
25
|
opa = ["opa-wasmtime>=0.1.1"]
|
|
23
26
|
|
|
@@ -27,13 +30,13 @@ dev = [
|
|
|
27
30
|
"commitizen>=4.13.9",
|
|
28
31
|
"pytest>=9.0.3",
|
|
29
32
|
"ruff>=0.17.00",
|
|
30
|
-
"uv-build>=0.
|
|
33
|
+
"uv-build>=0.13.0,<0.14.0",
|
|
31
34
|
"mkdocs-material>=9.7.0",
|
|
32
35
|
]
|
|
33
36
|
|
|
34
37
|
[tool.ruff]
|
|
35
38
|
target-version = "py310"
|
|
36
|
-
cache-dir = "
|
|
39
|
+
cache-dir = ".cache/ruff"
|
|
37
40
|
src = ["src"]
|
|
38
41
|
|
|
39
42
|
[tool.ruff.lint]
|
|
@@ -67,7 +70,7 @@ update_changelog_on_bump = true
|
|
|
67
70
|
|
|
68
71
|
[tool.pytest.ini_options]
|
|
69
72
|
addopts = "-v"
|
|
70
|
-
cache_dir = "
|
|
73
|
+
cache_dir = ".cache/pytest"
|
|
71
74
|
testpaths = ["tests"]
|
|
72
75
|
log_cli = true
|
|
73
76
|
log_cli_level = "INFO"
|
|
@@ -78,5 +81,5 @@ log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(linen
|
|
|
78
81
|
module-name = "jsonprofile"
|
|
79
82
|
|
|
80
83
|
[build-system]
|
|
81
|
-
requires = ["uv_build>=0.
|
|
84
|
+
requires = ["uv_build>=0.13.0,<0.14.0"]
|
|
82
85
|
build-backend = "uv_build"
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "jsonprofile"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Dynamic profile management for json files share same schema"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"bioregistry>=0.13.
|
|
9
|
-
"cachetools>=7.
|
|
8
|
+
"bioregistry>=0.13.67",
|
|
9
|
+
"cachetools>=7.2.8",
|
|
10
10
|
"email-validator>=2.3.0",
|
|
11
|
-
"httpx2>=
|
|
11
|
+
"httpx2>=2.14.0",
|
|
12
12
|
"jsonpath-ng>=1.8.0",
|
|
13
|
-
|
|
14
|
-
"jsonschema-rs>=0.46.9",
|
|
13
|
+
"jsonschema-rs>=0.60.3",
|
|
15
14
|
"ontology-lookup>=0.1.1",
|
|
16
|
-
"orjson>=3.
|
|
17
|
-
"pydantic>=2.
|
|
15
|
+
"orjson>=3.13.0",
|
|
16
|
+
"pydantic>=2.14.0",
|
|
18
17
|
"pyyaml>=6.0.3",
|
|
19
18
|
"rfc3986>=2.0.0",
|
|
20
19
|
]
|
|
20
|
+
[project.urls]
|
|
21
|
+
'Repository' = 'https://github.com/MetabolomicsHub/jsonprofile'
|
|
22
|
+
|
|
21
23
|
[project.optional-dependencies]
|
|
22
24
|
opa = ["opa-wasmtime>=0.1.1"]
|
|
23
25
|
|
|
@@ -27,13 +29,13 @@ dev = [
|
|
|
27
29
|
"commitizen>=4.13.9",
|
|
28
30
|
"pytest>=9.0.3",
|
|
29
31
|
"ruff>=0.17.00",
|
|
30
|
-
"uv-build>=0.
|
|
32
|
+
"uv-build>=0.13.0,<0.14.0",
|
|
31
33
|
"mkdocs-material>=9.7.0",
|
|
32
34
|
]
|
|
33
35
|
|
|
34
36
|
[tool.ruff]
|
|
35
37
|
target-version = "py310"
|
|
36
|
-
cache-dir = "
|
|
38
|
+
cache-dir = ".cache/ruff"
|
|
37
39
|
src = ["src"]
|
|
38
40
|
|
|
39
41
|
[tool.commitizen]
|
|
@@ -58,7 +60,7 @@ known-first-party = ["jsonprofile"]
|
|
|
58
60
|
|
|
59
61
|
[tool.pytest.ini_options]
|
|
60
62
|
addopts = "-v"
|
|
61
|
-
cache_dir = "
|
|
63
|
+
cache_dir = ".cache/pytest"
|
|
62
64
|
testpaths = ["tests"]
|
|
63
65
|
log_cli = true
|
|
64
66
|
log_cli_level = "INFO"
|
|
@@ -66,7 +68,7 @@ log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
|
|
66
68
|
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
|
|
67
69
|
|
|
68
70
|
[build-system]
|
|
69
|
-
requires = ["uv_build>=0.
|
|
71
|
+
requires = ["uv_build>=0.13.0,<0.14.0"]
|
|
70
72
|
build-backend = "uv_build"
|
|
71
73
|
|
|
72
74
|
[tool.uv.build-backend]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$defs": {
|
|
3
3
|
"BaseCvTerm": {
|
|
4
|
+
"additionalProperties": false,
|
|
4
5
|
"description": "A lightweight reference to a controlled vocabulary (CV) term.\n\nUsed within constraint definitions to identify specific CV terms\nby their label, accession, or human-readable name.",
|
|
5
6
|
"properties": {
|
|
6
7
|
"cv_label": {
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"type": "object"
|
|
48
49
|
},
|
|
49
50
|
"BooleanConstraint": {
|
|
51
|
+
"additionalProperties": false,
|
|
50
52
|
"description": "Validates that a value is boolean.",
|
|
51
53
|
"properties": {
|
|
52
54
|
"validator": {
|
|
@@ -260,6 +262,7 @@
|
|
|
260
262
|
"type": "object"
|
|
261
263
|
},
|
|
262
264
|
"CVListConstraint": {
|
|
265
|
+
"additionalProperties": false,
|
|
263
266
|
"description": "Restricts CV terms to those from specific controlled vocabularies.\n\nThe ``allowed_cv_list`` field specifies which CV namespace labels are\nallowed (e.g. ``['MS', 'UO']``).",
|
|
264
267
|
"properties": {
|
|
265
268
|
"validator": {
|
|
@@ -496,6 +499,7 @@
|
|
|
496
499
|
"type": "object"
|
|
497
500
|
},
|
|
498
501
|
"CVTermConstraint": {
|
|
502
|
+
"additionalProperties": false,
|
|
499
503
|
"description": "Base constraint for values that must be controlled vocabulary terms.\n\nSubclasses refine which CV terms are acceptable (by list, by parent\nterm, or by CV namespace). This class captures the common options\nshared across all CV term constraints.",
|
|
500
504
|
"properties": {
|
|
501
505
|
"validator": {
|
|
@@ -715,6 +719,7 @@
|
|
|
715
719
|
"type": "object"
|
|
716
720
|
},
|
|
717
721
|
"CVTermEnumConstraint": {
|
|
722
|
+
"additionalProperties": false,
|
|
718
723
|
"description": "Restricts the value to a specific set of CV terms.\n\nUnlike ``CVListConstraint`` which filters by CV namespace, this\nconstraint enumerates the exact terms that are acceptable.",
|
|
719
724
|
"properties": {
|
|
720
725
|
"validator": {
|
|
@@ -946,6 +951,7 @@
|
|
|
946
951
|
"type": "object"
|
|
947
952
|
},
|
|
948
953
|
"CVTermValueConstraint": {
|
|
954
|
+
"additionalProperties": false,
|
|
949
955
|
"description": "Validates the value slot of a specific CV term.\n\nWhen a field contains a CV term matching ``key_cv_term``, its\nvalue is validated against the nested ``value_constraint``.\nThis enables context-dependent validation (e.g. a numeric range\nthat only applies when a specific CV term is used).",
|
|
950
956
|
"properties": {
|
|
951
957
|
"validator": {
|
|
@@ -1209,6 +1215,7 @@
|
|
|
1209
1215
|
"type": "object"
|
|
1210
1216
|
},
|
|
1211
1217
|
"CollectionConstraint": {
|
|
1218
|
+
"additionalProperties": false,
|
|
1212
1219
|
"description": "Validates collection size and item matches against reference values.",
|
|
1213
1220
|
"properties": {
|
|
1214
1221
|
"validator": {
|
|
@@ -1591,6 +1598,7 @@
|
|
|
1591
1598
|
"type": "object"
|
|
1592
1599
|
},
|
|
1593
1600
|
"ConstraintGroup": {
|
|
1601
|
+
"additionalProperties": false,
|
|
1594
1602
|
"description": "Combines multiple constraints using boolean or valid-count logic.",
|
|
1595
1603
|
"properties": {
|
|
1596
1604
|
"validator": {
|
|
@@ -1875,6 +1883,7 @@
|
|
|
1875
1883
|
"type": "object"
|
|
1876
1884
|
},
|
|
1877
1885
|
"CustomConstraint": {
|
|
1886
|
+
"additionalProperties": false,
|
|
1878
1887
|
"description": "Runs a named custom constraint checker.",
|
|
1879
1888
|
"properties": {
|
|
1880
1889
|
"validator": {
|
|
@@ -2083,6 +2092,7 @@
|
|
|
2083
2092
|
"type": "object"
|
|
2084
2093
|
},
|
|
2085
2094
|
"CvTerm": {
|
|
2095
|
+
"additionalProperties": false,
|
|
2086
2096
|
"description": "Controlled vocabulary term with an optional user-provided value.",
|
|
2087
2097
|
"properties": {
|
|
2088
2098
|
"cv_label": {
|
|
@@ -2142,6 +2152,7 @@
|
|
|
2142
2152
|
"type": "object"
|
|
2143
2153
|
},
|
|
2144
2154
|
"CvTermFieldMapping": {
|
|
2155
|
+
"additionalProperties": false,
|
|
2145
2156
|
"properties": {
|
|
2146
2157
|
"label_field": {
|
|
2147
2158
|
"anyOf": [
|
|
@@ -2196,6 +2207,7 @@
|
|
|
2196
2207
|
"type": "object"
|
|
2197
2208
|
},
|
|
2198
2209
|
"DateTimeConstraint": {
|
|
2210
|
+
"additionalProperties": false,
|
|
2199
2211
|
"description": "Validates that a string value represents a valid date, time,\nor datetime.\n\nWhen ``format`` is provided, the value must match that specific\npattern (e.g. ISO-8601). When omitted, ISO-8601 is accepted.",
|
|
2200
2212
|
"properties": {
|
|
2201
2213
|
"validator": {
|
|
@@ -2376,7 +2388,8 @@
|
|
|
2376
2388
|
"type": "object"
|
|
2377
2389
|
},
|
|
2378
2390
|
"DecimalConstraint": {
|
|
2379
|
-
"
|
|
2391
|
+
"additionalProperties": false,
|
|
2392
|
+
"description": "Validates that a value is a valid decimal number.\n\nSupports optional range bounds, scientific notation control,\nand decimal precision limits.",
|
|
2380
2393
|
"properties": {
|
|
2381
2394
|
"validator": {
|
|
2382
2395
|
"anyOf": [
|
|
@@ -2541,6 +2554,8 @@
|
|
|
2541
2554
|
"minimum": {
|
|
2542
2555
|
"anyOf": [
|
|
2543
2556
|
{
|
|
2557
|
+
"maximum": 7.922816251426434e+28,
|
|
2558
|
+
"minimum": -7.922816251426434e+28,
|
|
2544
2559
|
"type": "number"
|
|
2545
2560
|
},
|
|
2546
2561
|
{
|
|
@@ -2557,6 +2572,8 @@
|
|
|
2557
2572
|
"maximum": {
|
|
2558
2573
|
"anyOf": [
|
|
2559
2574
|
{
|
|
2575
|
+
"maximum": 7.922816251426434e+28,
|
|
2576
|
+
"minimum": -7.922816251426434e+28,
|
|
2560
2577
|
"type": "number"
|
|
2561
2578
|
},
|
|
2562
2579
|
{
|
|
@@ -2629,6 +2646,7 @@
|
|
|
2629
2646
|
"type": "object"
|
|
2630
2647
|
},
|
|
2631
2648
|
"EmailConstraint": {
|
|
2649
|
+
"additionalProperties": false,
|
|
2632
2650
|
"description": "Validates that a string value is a well-formed email address.",
|
|
2633
2651
|
"properties": {
|
|
2634
2652
|
"validator": {
|
|
@@ -2820,6 +2838,7 @@
|
|
|
2820
2838
|
"type": "string"
|
|
2821
2839
|
},
|
|
2822
2840
|
"Evaluation": {
|
|
2841
|
+
"additionalProperties": false,
|
|
2823
2842
|
"description": "Evaluates a constraint against either the current value or another JSONPath.",
|
|
2824
2843
|
"properties": {
|
|
2825
2844
|
"root_value_evaluation": {
|
|
@@ -3005,6 +3024,7 @@
|
|
|
3005
3024
|
"type": "object"
|
|
3006
3025
|
},
|
|
3007
3026
|
"ExtendedCvTerm": {
|
|
3027
|
+
"additionalProperties": false,
|
|
3008
3028
|
"description": "Controlled vocabulary term whose value may be another CV term.",
|
|
3009
3029
|
"properties": {
|
|
3010
3030
|
"cv_label": {
|
|
@@ -3067,6 +3087,7 @@
|
|
|
3067
3087
|
"type": "object"
|
|
3068
3088
|
},
|
|
3069
3089
|
"FieldRequirement": {
|
|
3090
|
+
"additionalProperties": false,
|
|
3070
3091
|
"description": "Validation rules that apply to one profiled JSON field.",
|
|
3071
3092
|
"properties": {
|
|
3072
3093
|
"code": {
|
|
@@ -3239,6 +3260,7 @@
|
|
|
3239
3260
|
"type": "object"
|
|
3240
3261
|
},
|
|
3241
3262
|
"FieldRequirementGroup": {
|
|
3263
|
+
"additionalProperties": false,
|
|
3242
3264
|
"description": "Group of requirements combined by valid-count bounds.",
|
|
3243
3265
|
"properties": {
|
|
3244
3266
|
"code": {
|
|
@@ -3334,6 +3356,7 @@
|
|
|
3334
3356
|
"type": "object"
|
|
3335
3357
|
},
|
|
3336
3358
|
"IntegerConstraint": {
|
|
3359
|
+
"additionalProperties": false,
|
|
3337
3360
|
"description": "Validates that a value is a valid integer within an optional range.\n\nUse ``PositiveIntegerConstraint`` or ``NonNegativeIntegerConstraint``\nfor common presets.",
|
|
3338
3361
|
"properties": {
|
|
3339
3362
|
"validator": {
|
|
@@ -3499,6 +3522,8 @@
|
|
|
3499
3522
|
"minimum": {
|
|
3500
3523
|
"anyOf": [
|
|
3501
3524
|
{
|
|
3525
|
+
"maximum": 9223372036854775807,
|
|
3526
|
+
"minimum": -9223372036854775808,
|
|
3502
3527
|
"type": "integer"
|
|
3503
3528
|
},
|
|
3504
3529
|
{
|
|
@@ -3512,6 +3537,8 @@
|
|
|
3512
3537
|
"maximum": {
|
|
3513
3538
|
"anyOf": [
|
|
3514
3539
|
{
|
|
3540
|
+
"maximum": 9223372036854775807,
|
|
3541
|
+
"minimum": -9223372036854775808,
|
|
3515
3542
|
"type": "integer"
|
|
3516
3543
|
},
|
|
3517
3544
|
{
|
|
@@ -3527,6 +3554,7 @@
|
|
|
3527
3554
|
"type": "object"
|
|
3528
3555
|
},
|
|
3529
3556
|
"IntegerEnumConstraint": {
|
|
3557
|
+
"additionalProperties": false,
|
|
3530
3558
|
"description": "Validates that an integer value is one of a fixed set of allowed options.\n\nEach option maps an integer value to a display value, integer code,\nor ``ExtendedCvTerm`` metadata.",
|
|
3531
3559
|
"properties": {
|
|
3532
3560
|
"validator": {
|
|
@@ -3715,6 +3743,7 @@
|
|
|
3715
3743
|
"type": "object"
|
|
3716
3744
|
},
|
|
3717
3745
|
"JsonProfileConfiguration": {
|
|
3746
|
+
"additionalProperties": false,
|
|
3718
3747
|
"description": "Optional configuration shared by validators for a profile.",
|
|
3719
3748
|
"properties": {
|
|
3720
3749
|
"cv_term_field_mapping": {
|
|
@@ -3866,6 +3895,7 @@
|
|
|
3866
3895
|
"type": "object"
|
|
3867
3896
|
},
|
|
3868
3897
|
"NonNegativeIntegerConstraint": {
|
|
3898
|
+
"additionalProperties": false,
|
|
3869
3899
|
"description": "Validates that a value is a non-negative integer (>= 0).\n\nConvenience subclass of ``IntegerConstraint`` with ``minimum=0``.",
|
|
3870
3900
|
"properties": {
|
|
3871
3901
|
"validator": {
|
|
@@ -4031,6 +4061,8 @@
|
|
|
4031
4061
|
"minimum": {
|
|
4032
4062
|
"anyOf": [
|
|
4033
4063
|
{
|
|
4064
|
+
"maximum": 9223372036854775807,
|
|
4065
|
+
"minimum": -9223372036854775808,
|
|
4034
4066
|
"type": "integer"
|
|
4035
4067
|
},
|
|
4036
4068
|
{
|
|
@@ -4044,6 +4076,8 @@
|
|
|
4044
4076
|
"maximum": {
|
|
4045
4077
|
"anyOf": [
|
|
4046
4078
|
{
|
|
4079
|
+
"maximum": 9223372036854775807,
|
|
4080
|
+
"minimum": -9223372036854775808,
|
|
4047
4081
|
"type": "integer"
|
|
4048
4082
|
},
|
|
4049
4083
|
{
|
|
@@ -4059,6 +4093,7 @@
|
|
|
4059
4093
|
"type": "object"
|
|
4060
4094
|
},
|
|
4061
4095
|
"NotNullConstraint": {
|
|
4096
|
+
"additionalProperties": false,
|
|
4062
4097
|
"description": "Validates that a value is not null or one of the configured null values.",
|
|
4063
4098
|
"properties": {
|
|
4064
4099
|
"validator": {
|
|
@@ -4239,6 +4274,7 @@
|
|
|
4239
4274
|
"type": "object"
|
|
4240
4275
|
},
|
|
4241
4276
|
"OpaFieldRequirement": {
|
|
4277
|
+
"additionalProperties": false,
|
|
4242
4278
|
"description": "Requirement will be evaluated and messages are produced by OPA engine",
|
|
4243
4279
|
"properties": {
|
|
4244
4280
|
"code": {
|
|
@@ -4306,6 +4342,7 @@
|
|
|
4306
4342
|
"type": "object"
|
|
4307
4343
|
},
|
|
4308
4344
|
"OpaPolicyConstraint": {
|
|
4345
|
+
"additionalProperties": false,
|
|
4309
4346
|
"description": "Evaluates an Open Policy Agent policy against the input value.",
|
|
4310
4347
|
"properties": {
|
|
4311
4348
|
"validator": {
|
|
@@ -4494,6 +4531,7 @@
|
|
|
4494
4531
|
"type": "object"
|
|
4495
4532
|
},
|
|
4496
4533
|
"ParentCVTermConstraint": {
|
|
4534
|
+
"additionalProperties": false,
|
|
4497
4535
|
"description": "Restricts the value to CV terms that are descendants of\nspecified parent terms in the ontology hierarchy.\n\nSupports exclusion of specific terms or name patterns to\nfilter out unwanted branches of the ontology tree.",
|
|
4498
4536
|
"properties": {
|
|
4499
4537
|
"validator": {
|
|
@@ -4746,6 +4784,7 @@
|
|
|
4746
4784
|
"type": "object"
|
|
4747
4785
|
},
|
|
4748
4786
|
"PositiveIntegerConstraint": {
|
|
4787
|
+
"additionalProperties": false,
|
|
4749
4788
|
"description": "Validates that a value is a positive integer (>= 1).\n\nConvenience subclass of ``IntegerConstraint`` with ``minimum=1``.",
|
|
4750
4789
|
"properties": {
|
|
4751
4790
|
"validator": {
|
|
@@ -4911,6 +4950,8 @@
|
|
|
4911
4950
|
"minimum": {
|
|
4912
4951
|
"anyOf": [
|
|
4913
4952
|
{
|
|
4953
|
+
"maximum": 9223372036854775807,
|
|
4954
|
+
"minimum": -9223372036854775808,
|
|
4914
4955
|
"type": "integer"
|
|
4915
4956
|
},
|
|
4916
4957
|
{
|
|
@@ -4924,6 +4965,8 @@
|
|
|
4924
4965
|
"maximum": {
|
|
4925
4966
|
"anyOf": [
|
|
4926
4967
|
{
|
|
4968
|
+
"maximum": 9223372036854775807,
|
|
4969
|
+
"minimum": -9223372036854775808,
|
|
4927
4970
|
"type": "integer"
|
|
4928
4971
|
},
|
|
4929
4972
|
{
|
|
@@ -4939,6 +4982,7 @@
|
|
|
4939
4982
|
"type": "object"
|
|
4940
4983
|
},
|
|
4941
4984
|
"Precondition": {
|
|
4985
|
+
"additionalProperties": false,
|
|
4942
4986
|
"description": "Condition that controls whether a constraint should be evaluated.",
|
|
4943
4987
|
"properties": {
|
|
4944
4988
|
"evaluations": {
|
|
@@ -4995,6 +5039,7 @@
|
|
|
4995
5039
|
"type": "object"
|
|
4996
5040
|
},
|
|
4997
5041
|
"ProfileValidatorDefinition": {
|
|
5042
|
+
"additionalProperties": false,
|
|
4998
5043
|
"description": "Definition of a profile validator implementation.",
|
|
4999
5044
|
"properties": {
|
|
5000
5045
|
"label": {
|
|
@@ -5038,6 +5083,7 @@
|
|
|
5038
5083
|
"type": "object"
|
|
5039
5084
|
},
|
|
5040
5085
|
"RegexConstraint": {
|
|
5086
|
+
"additionalProperties": false,
|
|
5041
5087
|
"description": "Validates a string value against a regular expression pattern.\n\nUse for structured identifiers or format patterns that can be\nexpressed as a single regex (e.g. ORCID, adduct ion notation).",
|
|
5042
5088
|
"properties": {
|
|
5043
5089
|
"validator": {
|
|
@@ -5227,6 +5273,7 @@
|
|
|
5227
5273
|
"type": "object"
|
|
5228
5274
|
},
|
|
5229
5275
|
"StringConstraint": {
|
|
5276
|
+
"additionalProperties": false,
|
|
5230
5277
|
"description": "Validates that a string value's length falls within a range.\n\nAt least one of ``minimum`` or ``maximum`` should be set.",
|
|
5231
5278
|
"properties": {
|
|
5232
5279
|
"validator": {
|
|
@@ -5422,6 +5469,7 @@
|
|
|
5422
5469
|
"type": "object"
|
|
5423
5470
|
},
|
|
5424
5471
|
"StringEnumConstraint": {
|
|
5472
|
+
"additionalProperties": false,
|
|
5425
5473
|
"description": "Validates that a string value is one of a fixed set of allowed options.\n\nOptions may be a simple list of strings or a mapping from a string key\nto a display value, integer code, or ``ExtendedCvTerm`` metadata.",
|
|
5426
5474
|
"properties": {
|
|
5427
5475
|
"validator": {
|
|
@@ -5620,6 +5668,7 @@
|
|
|
5620
5668
|
"type": "object"
|
|
5621
5669
|
},
|
|
5622
5670
|
"UriConstraint": {
|
|
5671
|
+
"additionalProperties": false,
|
|
5623
5672
|
"description": "Validates that a string value is a well-formed URI.\n\nOptionally restricts accepted URI schemes (e.g. only ``https``,\n``ftp``).",
|
|
5624
5673
|
"properties": {
|
|
5625
5674
|
"validator": {
|
|
@@ -5803,6 +5852,7 @@
|
|
|
5803
5852
|
"type": "object"
|
|
5804
5853
|
},
|
|
5805
5854
|
"WasmFileDefinition": {
|
|
5855
|
+
"additionalProperties": false,
|
|
5806
5856
|
"description": "Configuration for an Open Policy Agent policy module.",
|
|
5807
5857
|
"properties": {
|
|
5808
5858
|
"wasm_file_download_url": {
|
|
@@ -5830,6 +5880,7 @@
|
|
|
5830
5880
|
"type": "object"
|
|
5831
5881
|
}
|
|
5832
5882
|
},
|
|
5883
|
+
"additionalProperties": false,
|
|
5833
5884
|
"description": "A JSON profile containing metadata, configuration, and field requirements.",
|
|
5834
5885
|
"properties": {
|
|
5835
5886
|
"id": {
|
|
@@ -14,6 +14,28 @@ from jsonprofile.profile.base import (
|
|
|
14
14
|
JsonProfileBaseModel,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
+
INT64_MIN = -(2**63)
|
|
18
|
+
INT64_MAX = 2**63 - 1
|
|
19
|
+
DECIMAL_MAX_COEFFICIENT = 2**96 - 1
|
|
20
|
+
DECIMAL_MAX_SCALE = 28
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def decimal_fits_shared_range(value: Decimal) -> bool:
|
|
24
|
+
"""Return whether a finite Decimal fits Rust Decimal's 96-bit representation."""
|
|
25
|
+
if not value.is_finite():
|
|
26
|
+
return False
|
|
27
|
+
parts = value.as_tuple()
|
|
28
|
+
coefficient = int("".join(map(str, parts.digits)) or "0")
|
|
29
|
+
exponent = parts.exponent
|
|
30
|
+
scale = max(0, -exponent)
|
|
31
|
+
if scale > DECIMAL_MAX_SCALE:
|
|
32
|
+
return False
|
|
33
|
+
if exponent > 0 and coefficient:
|
|
34
|
+
if len(str(coefficient)) + exponent > len(str(DECIMAL_MAX_COEFFICIENT)):
|
|
35
|
+
return False
|
|
36
|
+
coefficient *= 10**exponent
|
|
37
|
+
return coefficient <= DECIMAL_MAX_COEFFICIENT
|
|
38
|
+
|
|
17
39
|
|
|
18
40
|
class Precondition(JsonProfileBaseModel):
|
|
19
41
|
"""Condition that controls whether a constraint should be evaluated."""
|
|
@@ -306,6 +328,13 @@ class IntegerEnumConstraint(Constraint):
|
|
|
306
328
|
),
|
|
307
329
|
]
|
|
308
330
|
|
|
331
|
+
@field_validator("options")
|
|
332
|
+
@classmethod
|
|
333
|
+
def int64_option_keys(cls, value):
|
|
334
|
+
if any(key < INT64_MIN or key > INT64_MAX for key in value):
|
|
335
|
+
raise ValueError("integer-enum option keys must fit signed 64-bit range")
|
|
336
|
+
return value
|
|
337
|
+
|
|
309
338
|
|
|
310
339
|
class IntegerConstraint(Constraint):
|
|
311
340
|
"""Validates that a value is a valid integer within an optional range.
|
|
@@ -320,13 +349,17 @@ class IntegerConstraint(Constraint):
|
|
|
320
349
|
minimum: Annotated[
|
|
321
350
|
Optional[int],
|
|
322
351
|
Field(
|
|
323
|
-
description="Minimum allowed value (inclusive). None means no lower bound."
|
|
352
|
+
description="Minimum allowed value (inclusive). None means no lower bound.",
|
|
353
|
+
ge=INT64_MIN,
|
|
354
|
+
le=INT64_MAX,
|
|
324
355
|
),
|
|
325
356
|
] = None
|
|
326
357
|
maximum: Annotated[
|
|
327
358
|
Optional[int],
|
|
328
359
|
Field(
|
|
329
|
-
description="Maximum allowed value (inclusive). None means no upper bound."
|
|
360
|
+
description="Maximum allowed value (inclusive). None means no upper bound.",
|
|
361
|
+
ge=INT64_MIN,
|
|
362
|
+
le=INT64_MAX,
|
|
330
363
|
),
|
|
331
364
|
] = None
|
|
332
365
|
|
|
@@ -342,7 +375,11 @@ class PositiveIntegerConstraint(IntegerConstraint):
|
|
|
342
375
|
)
|
|
343
376
|
minimum: Annotated[
|
|
344
377
|
Optional[int],
|
|
345
|
-
Field(
|
|
378
|
+
Field(
|
|
379
|
+
description="Minimum allowed value (inclusive). Defaults to 1.",
|
|
380
|
+
ge=INT64_MIN,
|
|
381
|
+
le=INT64_MAX,
|
|
382
|
+
),
|
|
346
383
|
] = 1
|
|
347
384
|
|
|
348
385
|
|
|
@@ -357,7 +394,11 @@ class NonNegativeIntegerConstraint(IntegerConstraint):
|
|
|
357
394
|
)
|
|
358
395
|
minimum: Annotated[
|
|
359
396
|
Optional[int],
|
|
360
|
-
Field(
|
|
397
|
+
Field(
|
|
398
|
+
description="Minimum allowed value (inclusive). Defaults to 0.",
|
|
399
|
+
ge=INT64_MIN,
|
|
400
|
+
le=INT64_MAX,
|
|
401
|
+
),
|
|
361
402
|
] = 0
|
|
362
403
|
|
|
363
404
|
|
|
@@ -385,7 +426,7 @@ class BooleanConstraint(Constraint):
|
|
|
385
426
|
|
|
386
427
|
|
|
387
428
|
class DecimalConstraint(Constraint):
|
|
388
|
-
"""Validates that a value is a valid
|
|
429
|
+
"""Validates that a value is a valid decimal number.
|
|
389
430
|
|
|
390
431
|
Supports optional range bounds, scientific notation control,
|
|
391
432
|
and decimal precision limits.
|
|
@@ -397,15 +438,29 @@ class DecimalConstraint(Constraint):
|
|
|
397
438
|
minimum: Annotated[
|
|
398
439
|
Optional[Decimal],
|
|
399
440
|
Field(
|
|
400
|
-
description="Minimum allowed value (inclusive). None means no lower bound."
|
|
441
|
+
description="Minimum allowed value (inclusive). None means no lower bound.",
|
|
442
|
+
ge=Decimal(-DECIMAL_MAX_COEFFICIENT),
|
|
443
|
+
le=Decimal(DECIMAL_MAX_COEFFICIENT),
|
|
401
444
|
),
|
|
402
445
|
] = None
|
|
403
446
|
maximum: Annotated[
|
|
404
447
|
Optional[Decimal],
|
|
405
448
|
Field(
|
|
406
|
-
description="Maximum allowed value (inclusive). None means no upper bound."
|
|
449
|
+
description="Maximum allowed value (inclusive). None means no upper bound.",
|
|
450
|
+
ge=Decimal(-DECIMAL_MAX_COEFFICIENT),
|
|
451
|
+
le=Decimal(DECIMAL_MAX_COEFFICIENT),
|
|
407
452
|
),
|
|
408
453
|
] = None
|
|
454
|
+
|
|
455
|
+
@field_validator("minimum", "maximum")
|
|
456
|
+
@classmethod
|
|
457
|
+
def bounds_fit_shared_decimal_range(cls, value):
|
|
458
|
+
if value is not None and not decimal_fits_shared_range(value):
|
|
459
|
+
raise ValueError(
|
|
460
|
+
"decimal bounds must fit a 96-bit coefficient and scale <= 28"
|
|
461
|
+
)
|
|
462
|
+
return value
|
|
463
|
+
|
|
409
464
|
allow_scientific_notation: Annotated[
|
|
410
465
|
Optional[bool],
|
|
411
466
|
Field(
|
|
@@ -9,6 +9,7 @@ import jsonpath_ng
|
|
|
9
9
|
from jsonprofile.profile.base import JsonPath, JsonProfileBaseModel
|
|
10
10
|
from jsonprofile.profile.constraints.constraints import (
|
|
11
11
|
Constraint,
|
|
12
|
+
CustomConstraint,
|
|
12
13
|
DecimalConstraint,
|
|
13
14
|
)
|
|
14
15
|
|
|
@@ -204,22 +205,39 @@ class ConstraintChecker(abc.ABC):
|
|
|
204
205
|
|
|
205
206
|
sub_value = self.get_json_path_value(constraint, value, context)
|
|
206
207
|
if success:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
if isinstance(constraint, CustomConstraint):
|
|
209
|
+
null_value = sub_value is None or (
|
|
210
|
+
constraint.null_values
|
|
211
|
+
and str(sub_value) in constraint.null_values
|
|
212
|
+
)
|
|
213
|
+
exceptional = constraint.exceptional_values or []
|
|
214
|
+
if sub_value in exceptional or (null_value and None in exceptional):
|
|
215
|
+
is_valid, message = True, "value is in exceptional list"
|
|
216
|
+
else:
|
|
217
|
+
is_valid, message = checker.validate(
|
|
218
|
+
constraint,
|
|
219
|
+
None if null_value else sub_value,
|
|
220
|
+
root=root,
|
|
221
|
+
context=context,
|
|
222
|
+
)
|
|
223
|
+
if constraint.negated:
|
|
224
|
+
is_valid = not is_valid
|
|
225
|
+
message = message or ""
|
|
226
|
+
else:
|
|
227
|
+
is_valid, message = checker.validate(
|
|
228
|
+
constraint, sub_value, root=root, context=context
|
|
229
|
+
)
|
|
210
230
|
else:
|
|
211
231
|
is_valid = True
|
|
212
232
|
message = f"Precondition does not meet: {message}"
|
|
213
233
|
return ConstraintValidationResult(
|
|
214
234
|
is_valid=is_valid,
|
|
215
235
|
message=message,
|
|
216
|
-
constraint_name=getattr(constraint, "name", "unknown"),
|
|
217
236
|
)
|
|
218
237
|
|
|
219
238
|
return ConstraintValidationResult(
|
|
220
239
|
is_valid=False,
|
|
221
240
|
message=f"Unknown constraint type: {type(constraint)}",
|
|
222
|
-
constraint_name=getattr(constraint, "name", "unknown"),
|
|
223
241
|
)
|
|
224
242
|
|
|
225
243
|
def get_json_path_value(
|
{jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/checkers/default_checker.py
RENAMED
|
@@ -19,6 +19,8 @@ from jsonprofile.profile.base import (
|
|
|
19
19
|
ValidationRuntimeConfiguration,
|
|
20
20
|
)
|
|
21
21
|
from jsonprofile.profile.constraints.constraints import (
|
|
22
|
+
INT64_MAX,
|
|
23
|
+
INT64_MIN,
|
|
22
24
|
BooleanConstraint,
|
|
23
25
|
CollectionConstraint,
|
|
24
26
|
ConstraintGroup,
|
|
@@ -40,6 +42,7 @@ from jsonprofile.profile.constraints.constraints import (
|
|
|
40
42
|
StringConstraint,
|
|
41
43
|
StringEnumConstraint,
|
|
42
44
|
UriConstraint,
|
|
45
|
+
decimal_fits_shared_range,
|
|
43
46
|
)
|
|
44
47
|
from jsonprofile.profile.model import (
|
|
45
48
|
JsonProfileConfiguration,
|
|
@@ -52,6 +55,14 @@ from jsonprofile.validator.decorators import constraint_checker
|
|
|
52
55
|
logger = logging.getLogger(__name__)
|
|
53
56
|
|
|
54
57
|
|
|
58
|
+
def _parse_int64(value: str) -> int | None:
|
|
59
|
+
try:
|
|
60
|
+
parsed = int(value)
|
|
61
|
+
except (ValueError, TypeError, OverflowError):
|
|
62
|
+
return None
|
|
63
|
+
return parsed if INT64_MIN <= parsed <= INT64_MAX else None
|
|
64
|
+
|
|
65
|
+
|
|
55
66
|
def _extract_cv_info(value: Any, mapping: CvTermFieldMapping) -> CvTerm:
|
|
56
67
|
"""Extracts label, accession, name, and value from a CvTerm or dict."""
|
|
57
68
|
if isinstance(value, CvTerm):
|
|
@@ -95,16 +106,23 @@ class NotNullConstraintChecker(ConstraintChecker):
|
|
|
95
106
|
) -> Tuple[bool, Optional[str]]:
|
|
96
107
|
evaluation = False
|
|
97
108
|
messages = []
|
|
109
|
+
is_exceptional = bool(
|
|
110
|
+
constraint.exceptional_values and value in constraint.exceptional_values
|
|
111
|
+
)
|
|
98
112
|
if value is not None and constraint.null_values:
|
|
99
113
|
str_val = str(value) if constraint.case_sensitive else str(value).lower()
|
|
100
114
|
if str_val in constraint.null_values:
|
|
101
115
|
messages.append("value is in null value list")
|
|
102
116
|
value = None
|
|
103
117
|
message = ""
|
|
104
|
-
if
|
|
118
|
+
if is_exceptional:
|
|
119
|
+
evaluation = True
|
|
120
|
+
message = "value is in exceptional list"
|
|
121
|
+
elif value is not None:
|
|
105
122
|
evaluation = True
|
|
106
123
|
message = f"{value} is not null"
|
|
107
|
-
|
|
124
|
+
elif messages:
|
|
125
|
+
message = ". ".join(messages)
|
|
108
126
|
|
|
109
127
|
if constraint.negated:
|
|
110
128
|
evaluation = not evaluation
|
|
@@ -275,7 +293,9 @@ class CollectionConstraintChecker(ConstraintChecker):
|
|
|
275
293
|
message = "value is not defined"
|
|
276
294
|
elif not isinstance(collection_values, list):
|
|
277
295
|
message = f"{constraint.json_path} is not a collection."
|
|
278
|
-
|
|
296
|
+
if constraint.negated:
|
|
297
|
+
return True, self.format_message(constraint, message)
|
|
298
|
+
return False, self.format_message(constraint, message)
|
|
279
299
|
else:
|
|
280
300
|
val = collection_values
|
|
281
301
|
if constraint.exceptional_values and (
|
|
@@ -526,10 +546,7 @@ class IntegerEnumConstraintChecker(ConstraintChecker):
|
|
|
526
546
|
):
|
|
527
547
|
evaluation = True
|
|
528
548
|
else:
|
|
529
|
-
|
|
530
|
-
int_val = int(val)
|
|
531
|
-
except: # noqa: E722
|
|
532
|
-
int_val = None
|
|
549
|
+
int_val = _parse_int64(val)
|
|
533
550
|
if int_val is not None:
|
|
534
551
|
if int_val in constraint.options:
|
|
535
552
|
evaluation = True
|
|
@@ -575,10 +592,7 @@ class IntegerConstraintChecker(ConstraintChecker):
|
|
|
575
592
|
min_req = True
|
|
576
593
|
max_req = True
|
|
577
594
|
else:
|
|
578
|
-
|
|
579
|
-
int_val = int(val)
|
|
580
|
-
except: # noqa: E722
|
|
581
|
-
int_val = None
|
|
595
|
+
int_val = _parse_int64(val)
|
|
582
596
|
if int_val is not None:
|
|
583
597
|
min_req = (
|
|
584
598
|
True
|
|
@@ -724,13 +738,18 @@ class DecimalConstraintChecker(ConstraintChecker):
|
|
|
724
738
|
pass
|
|
725
739
|
if decimal_val is not None:
|
|
726
740
|
if decimal_val.is_finite():
|
|
727
|
-
if (
|
|
728
|
-
|
|
741
|
+
if not decimal_fits_shared_range(decimal_val):
|
|
742
|
+
message = (
|
|
743
|
+
"value is outside the shared decimal range "
|
|
744
|
+
"(96-bit coefficient, scale <= 28)"
|
|
745
|
+
)
|
|
746
|
+
elif (
|
|
747
|
+
constraint.allow_scientific_notation is False
|
|
729
748
|
and "e" in val.lower()
|
|
730
749
|
):
|
|
731
750
|
message = "scientific notation is not allowed"
|
|
732
751
|
else:
|
|
733
|
-
scale = -decimal_val.as_tuple().exponent
|
|
752
|
+
scale = max(0, -decimal_val.as_tuple().exponent)
|
|
734
753
|
min_scale_req = (
|
|
735
754
|
True
|
|
736
755
|
if constraint.min_scale is None
|
|
@@ -966,40 +985,34 @@ class CVTermConstraintChecker(ConstraintChecker):
|
|
|
966
985
|
and param.cv_label in constraint.exceptional_cv_list
|
|
967
986
|
):
|
|
968
987
|
message = "CV source is in exception list"
|
|
969
|
-
|
|
970
|
-
is_user_defined = (
|
|
971
|
-
not param.cv_label
|
|
972
|
-
and not param.cv_accession
|
|
973
|
-
and param.name
|
|
974
|
-
and len(param.name) > 0
|
|
975
|
-
)
|
|
976
|
-
if is_user_defined:
|
|
977
|
-
if constraint.allow_user_defined_terms:
|
|
978
|
-
name_req = True
|
|
979
|
-
message = "User-defined terms is allowed."
|
|
980
|
-
else:
|
|
981
|
-
message = "User-defined terms is not allowed."
|
|
988
|
+
name_req = True
|
|
982
989
|
else:
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
990
|
+
is_user_defined = (
|
|
991
|
+
not param.cv_label
|
|
992
|
+
and not param.cv_accession
|
|
993
|
+
and param.name
|
|
994
|
+
and len(param.name) > 0
|
|
995
|
+
)
|
|
996
|
+
if is_user_defined:
|
|
997
|
+
if constraint.allow_user_defined_terms:
|
|
998
|
+
name_req = True
|
|
999
|
+
message = "User-defined terms is allowed."
|
|
986
1000
|
else:
|
|
987
|
-
|
|
1001
|
+
message = "User-defined terms is not allowed."
|
|
1002
|
+
else:
|
|
1003
|
+
if param.name:
|
|
1004
|
+
if not param.cv_label or not param.cv_accession:
|
|
1005
|
+
message = f"invalid cv term label or accession. {param}"
|
|
1006
|
+
elif runtime_config and runtime_config.offline_mode:
|
|
988
1007
|
name_req = True
|
|
989
1008
|
message = "term is valid."
|
|
990
1009
|
else:
|
|
991
|
-
|
|
1010
|
+
name_req, message = cv_term_search.check_cv_term(
|
|
992
1011
|
param,
|
|
993
|
-
allow_synonym_search=
|
|
994
|
-
if constraint.allow_synonym
|
|
995
|
-
else False,
|
|
1012
|
+
allow_synonym_search=bool(constraint.allow_synonym),
|
|
996
1013
|
)
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
)
|
|
1000
|
-
|
|
1001
|
-
else:
|
|
1002
|
-
message = f"invalid cv term name: {param}"
|
|
1014
|
+
else:
|
|
1015
|
+
message = f"invalid cv term name: {param}"
|
|
1003
1016
|
|
|
1004
1017
|
messages = [message] if message else []
|
|
1005
1018
|
|
|
@@ -1063,6 +1076,13 @@ class CVListConstraintChecker(ConstraintChecker):
|
|
|
1063
1076
|
message = (
|
|
1064
1077
|
f"'{param}' term is in exception cv term list."
|
|
1065
1078
|
)
|
|
1079
|
+
if (
|
|
1080
|
+
not name_req
|
|
1081
|
+
and constraint.exceptional_cv_list
|
|
1082
|
+
and param.cv_label in constraint.exceptional_cv_list
|
|
1083
|
+
):
|
|
1084
|
+
name_req = True
|
|
1085
|
+
message = "CV source is in exception list"
|
|
1066
1086
|
if not name_req:
|
|
1067
1087
|
if constraint.allowed_cv_list:
|
|
1068
1088
|
if param.cv_label.upper() in [
|
|
@@ -1080,9 +1100,7 @@ class CVListConstraintChecker(ConstraintChecker):
|
|
|
1080
1100
|
else False,
|
|
1081
1101
|
)
|
|
1082
1102
|
)
|
|
1083
|
-
|
|
1084
|
-
constraint, message
|
|
1085
|
-
)
|
|
1103
|
+
name_req = verified
|
|
1086
1104
|
else:
|
|
1087
1105
|
message = (
|
|
1088
1106
|
f"'{param}' not in the selected cv list: "
|
|
@@ -1099,9 +1117,7 @@ class CVListConstraintChecker(ConstraintChecker):
|
|
|
1099
1117
|
if constraint.allow_synonym
|
|
1100
1118
|
else False,
|
|
1101
1119
|
)
|
|
1102
|
-
|
|
1103
|
-
constraint, message
|
|
1104
|
-
)
|
|
1120
|
+
name_req = verified
|
|
1105
1121
|
|
|
1106
1122
|
else:
|
|
1107
1123
|
message = "invalid cv term name."
|
|
@@ -1240,40 +1256,34 @@ class ParentCVTermConstraintChecker(ConstraintChecker):
|
|
|
1240
1256
|
if param in constraint.exceptional_values:
|
|
1241
1257
|
name_req = True
|
|
1242
1258
|
message = "cv term is in exception list."
|
|
1259
|
+
if (
|
|
1260
|
+
not name_req
|
|
1261
|
+
and constraint.exceptional_cv_list
|
|
1262
|
+
and param.cv_label in constraint.exceptional_cv_list
|
|
1263
|
+
):
|
|
1264
|
+
name_req = True
|
|
1265
|
+
message = "CV source is in exception list"
|
|
1243
1266
|
if not name_req:
|
|
1244
1267
|
if runtime_config and runtime_config.offline_mode:
|
|
1245
1268
|
name_req = True
|
|
1246
1269
|
message = "offline mode."
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
for parent in constraint.parent_cv_terms or []:
|
|
1270
|
+
elif constraint.parent_cv_terms:
|
|
1271
|
+
for parent in constraint.parent_cv_terms:
|
|
1250
1272
|
verified, message = cv_term_search.check_cv_term(
|
|
1251
1273
|
cv_term=param,
|
|
1252
1274
|
parent_cv_term=parent,
|
|
1253
|
-
allow_synonym_search=
|
|
1254
|
-
|
|
1255
|
-
|
|
1275
|
+
allow_synonym_search=bool(
|
|
1276
|
+
constraint.allow_synonym
|
|
1277
|
+
),
|
|
1256
1278
|
)
|
|
1257
1279
|
if verified:
|
|
1258
|
-
|
|
1280
|
+
name_req = True
|
|
1281
|
+
message = (
|
|
1282
|
+
f"CV term {param} is parent of {parent}."
|
|
1283
|
+
)
|
|
1259
1284
|
break
|
|
1260
|
-
if
|
|
1261
|
-
name_req = True
|
|
1262
|
-
message = (
|
|
1263
|
-
f"CV term {param} is "
|
|
1264
|
-
f"parent of {verified_parent}."
|
|
1265
|
-
)
|
|
1266
|
-
else:
|
|
1285
|
+
if not name_req:
|
|
1267
1286
|
message = "term in not child of the parent."
|
|
1268
|
-
|
|
1269
|
-
if constraint.parent_cv_terms:
|
|
1270
|
-
input_val = BaseCvTerm.model_validate(
|
|
1271
|
-
param, from_attributes=True
|
|
1272
|
-
)
|
|
1273
|
-
if input_val in (constraint.exceptional_values or []):
|
|
1274
|
-
name_req = True
|
|
1275
|
-
else:
|
|
1276
|
-
message = "not child of the parent."
|
|
1277
1287
|
else:
|
|
1278
1288
|
name_req = True
|
|
1279
1289
|
message = "not child of the parent."
|
{jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/default/ols_cv_term_search.py
RENAMED
|
@@ -15,6 +15,10 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
_T = TypeVar("T")
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class _OlsSearchFailure(RuntimeError):
|
|
19
|
+
"""An unsuccessful OLS response that must not be kept in the result cache."""
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
class _OlsSearchModel(JsonProfileBaseModel, Generic[_T]):
|
|
19
23
|
page: int
|
|
20
24
|
num_elements: int
|
|
@@ -53,18 +57,17 @@ class _ChildrenSearchModel(JsonProfileBaseModel):
|
|
|
53
57
|
def _search_ols(
|
|
54
58
|
url: str, params: dict, headers: dict, timeout: int = 10
|
|
55
59
|
) -> tuple[int, dict[str, Any]]:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return 500, {"error": str(ex)}
|
|
60
|
+
result = httpx2.get(url, params=params, headers=headers, timeout=timeout)
|
|
61
|
+
if result.status_code in {200, 201}:
|
|
62
|
+
return result.status_code, result.json()
|
|
63
|
+
if result.status_code == 404:
|
|
64
|
+
return result.status_code, {}
|
|
62
65
|
logger.warning(
|
|
63
66
|
"Could not find CV term: %s %s",
|
|
64
67
|
result.status_code,
|
|
65
68
|
orjson.dumps(params, orjson.OPT_SORT_KEYS),
|
|
66
69
|
)
|
|
67
|
-
|
|
70
|
+
raise _OlsSearchFailure(f"OLS returned HTTP {result.status_code} for {url}")
|
|
68
71
|
|
|
69
72
|
|
|
70
73
|
class OlsCvTermSearch(CvTermSearch):
|
|
@@ -125,7 +125,7 @@ class JsonValidator:
|
|
|
125
125
|
logger.debug("Json schema will be loaded.")
|
|
126
126
|
self.json_schema = None
|
|
127
127
|
self.schema_validator = None
|
|
128
|
-
if json_schema:
|
|
128
|
+
if json_schema is not None:
|
|
129
129
|
self.json_schema = self.load_jsonschema(json_schema)
|
|
130
130
|
self.validate_jsonschema(self.json_schema)
|
|
131
131
|
logger.debug("Json schema validated.")
|
|
@@ -233,7 +233,7 @@ class JsonValidator:
|
|
|
233
233
|
elif isinstance(profile, dict):
|
|
234
234
|
target_profile = JsonProfile.model_validate(profile, by_alias=True)
|
|
235
235
|
if isinstance(profile, JsonProfile):
|
|
236
|
-
target_profile = profile
|
|
236
|
+
target_profile = profile.model_copy(deep=True)
|
|
237
237
|
if not target_profile or not isinstance(target_profile, JsonProfile):
|
|
238
238
|
raise ValueError(
|
|
239
239
|
"Json profile load failure. "
|
|
@@ -284,6 +284,8 @@ class JsonValidator:
|
|
|
284
284
|
|
|
285
285
|
@staticmethod
|
|
286
286
|
def extend_profile(parent_profile: None | JsonProfile, input_profile: JsonProfile):
|
|
287
|
+
# Merge into a detached copy because profiles may be reused or cached.
|
|
288
|
+
input_profile = input_profile.model_copy(deep=True)
|
|
287
289
|
if not parent_profile:
|
|
288
290
|
if not input_profile.configuration:
|
|
289
291
|
input_profile.configuration = JsonProfileConfiguration(
|
|
@@ -301,16 +303,21 @@ class JsonValidator:
|
|
|
301
303
|
parent_profile.name,
|
|
302
304
|
)
|
|
303
305
|
new_requirements = input_profile.requirements or {}
|
|
304
|
-
input_profile.requirements =
|
|
306
|
+
input_profile.requirements = {
|
|
307
|
+
key: value.model_copy(deep=True) if value is not None else None
|
|
308
|
+
for key, value in (parent_profile.requirements or {}).items()
|
|
309
|
+
}
|
|
305
310
|
|
|
306
311
|
if not input_profile.configuration:
|
|
307
312
|
input_profile.configuration = (
|
|
308
313
|
parent_profile.configuration or JsonProfileConfiguration()
|
|
309
|
-
)
|
|
314
|
+
).model_copy(deep=True)
|
|
310
315
|
|
|
311
316
|
# set configuration fields from parent if they are not defined.
|
|
312
317
|
config = input_profile.configuration
|
|
313
|
-
parent_config =
|
|
318
|
+
parent_config = (
|
|
319
|
+
parent_profile.configuration or JsonProfileConfiguration()
|
|
320
|
+
).model_copy(deep=True)
|
|
314
321
|
for field, _ in config.__class__.model_fields.items():
|
|
315
322
|
val = getattr(config, field)
|
|
316
323
|
if val is None:
|
|
@@ -526,8 +533,9 @@ class JsonValidator:
|
|
|
526
533
|
process_reporting_threshold_in_secs: float = 5.0,
|
|
527
534
|
) -> JsonValidationResult:
|
|
528
535
|
id_fields = self.json_profile.configuration.object_unique_id_fields
|
|
529
|
-
|
|
530
|
-
runtime_config
|
|
536
|
+
runtime_config = (
|
|
537
|
+
runtime_config or ValidationRuntimeConfiguration()
|
|
538
|
+
).model_copy(deep=True)
|
|
531
539
|
unique_id_objects = {}
|
|
532
540
|
if id_fields:
|
|
533
541
|
unique_id_objects = self.update_unique_object_dict(
|
|
@@ -973,7 +981,9 @@ class JsonValidator:
|
|
|
973
981
|
)
|
|
974
982
|
|
|
975
983
|
if field_requirement.value_constraint:
|
|
976
|
-
constraint = field_requirement.value_constraint
|
|
984
|
+
constraint = field_requirement.value_constraint.model_copy(
|
|
985
|
+
deep=True
|
|
986
|
+
)
|
|
977
987
|
checker = self.profile_validator_factory.get_checker(constraint)
|
|
978
988
|
skip = False
|
|
979
989
|
if runtime_config and runtime_config.skip_decimal_validations:
|
|
@@ -981,12 +991,10 @@ class JsonValidator:
|
|
|
981
991
|
skip = True
|
|
982
992
|
|
|
983
993
|
if not skip:
|
|
984
|
-
if not
|
|
985
|
-
|
|
986
|
-
field_requirement.description
|
|
987
|
-
)
|
|
994
|
+
if not constraint.description:
|
|
995
|
+
constraint.description = field_requirement.description
|
|
988
996
|
res = checker.validate_constraint(
|
|
989
|
-
constraint=
|
|
997
|
+
constraint=constraint,
|
|
990
998
|
value=x.value,
|
|
991
999
|
root=input_json,
|
|
992
1000
|
context=context,
|
|
@@ -5,6 +5,7 @@ import re
|
|
|
5
5
|
import shutil
|
|
6
6
|
import tarfile
|
|
7
7
|
import tempfile
|
|
8
|
+
import threading
|
|
8
9
|
import time
|
|
9
10
|
from datetime import datetime, timezone
|
|
10
11
|
from pathlib import Path
|
|
@@ -222,6 +223,7 @@ def _builtin_time_parse_ns(*args):
|
|
|
222
223
|
|
|
223
224
|
class LocalOpaEngine(OpaEngine):
|
|
224
225
|
def __init__(self, wasm_path: str):
|
|
226
|
+
self._evaluation_lock = threading.RLock()
|
|
225
227
|
try:
|
|
226
228
|
self._bundle_data = None
|
|
227
229
|
if tarfile.is_tarfile(wasm_path):
|
|
@@ -396,23 +398,24 @@ class LocalOpaEngine(OpaEngine):
|
|
|
396
398
|
bundle data loaded from data.json is restored if available.
|
|
397
399
|
"""
|
|
398
400
|
input_data = _sanitize_json_value(input_data)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
self.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
401
|
+
with self._evaluation_lock:
|
|
402
|
+
if data is not None:
|
|
403
|
+
self.policy.set_data(_sanitize_json_value(data))
|
|
404
|
+
elif self._bundle_data is not None:
|
|
405
|
+
self.policy.set_data(_sanitize_json_value(self._bundle_data))
|
|
406
|
+
entrypoint = self.resolve_entrypoint(entrypoint)
|
|
407
|
+
if entrypoint is None:
|
|
408
|
+
result = self.policy.evaluate(input_data)
|
|
409
|
+
else:
|
|
410
|
+
result = self.policy.evaluate(input_data, entrypoint=entrypoint)
|
|
411
|
+
# Return the raw OPA evaluation result for caller inspection.
|
|
412
|
+
return result[0].get("result") if result and result[0] else []
|
|
411
413
|
|
|
412
414
|
|
|
413
415
|
class LocalOpaEngineFactory(OpaEngineFactory):
|
|
414
416
|
def __init__(self):
|
|
415
417
|
self.engines: dict[Path, OpaEngine] = {}
|
|
418
|
+
self._engines_lock = threading.RLock()
|
|
416
419
|
|
|
417
420
|
def load_from_url(self, url: str, file_path: str) -> None:
|
|
418
421
|
"""Download a file from a URL and save it to the given file path.
|
|
@@ -441,30 +444,31 @@ class LocalOpaEngineFactory(OpaEngineFactory):
|
|
|
441
444
|
wasm_file_path: str,
|
|
442
445
|
wasm_file_download_url: Optional[str] = None,
|
|
443
446
|
):
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
447
|
+
with self._engines_lock:
|
|
448
|
+
if not wasm_file_path:
|
|
449
|
+
raise ValueError("Invalid WASM file path")
|
|
450
|
+
wasm_path = Path(wasm_file_path)
|
|
451
|
+
if not wasm_path.exists():
|
|
452
|
+
alt_parent = Path("..") / wasm_path
|
|
453
|
+
alt_stripped = Path(wasm_file_path.removeprefix("../"))
|
|
454
|
+
if alt_parent.exists():
|
|
455
|
+
wasm_path = alt_parent
|
|
456
|
+
wasm_file_path = str(alt_parent)
|
|
457
|
+
elif alt_stripped.exists():
|
|
458
|
+
wasm_path = alt_stripped
|
|
459
|
+
wasm_file_path = str(alt_stripped)
|
|
460
|
+
if wasm_path in self.engines:
|
|
461
|
+
return self.engines[wasm_path]
|
|
462
|
+
|
|
463
|
+
if not wasm_path.exists() and not wasm_file_download_url:
|
|
464
|
+
raise ValueError(
|
|
465
|
+
"Invalid WASM file does not exists and URL is not defined. "
|
|
466
|
+
"Select a valid file path or a valid URL to download WASM file"
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
if not wasm_path.exists():
|
|
470
|
+
self.load_from_url(wasm_file_download_url, wasm_file_path)
|
|
471
|
+
|
|
472
|
+
engine = LocalOpaEngine(wasm_path=wasm_file_path)
|
|
473
|
+
self.engines[wasm_path] = engine
|
|
474
|
+
return engine
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/checkers/custom_checker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jsonprofile-0.2.0 → jsonprofile-0.2.2}/src/jsonprofile/validator/default/profile_validator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|