diff --git a/.editorconfig b/.editorconfig index 923b23e2..f5904377 100644 --- a/.editorconfig +++ b/.editorconfig @@ -24,10 +24,14 @@ include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true -[*.{html,css,scss,json,yml}] +[*.{html,css,scss,yml}] indent_style = space indent_size = 2 +[*.json] +indent_style = space +indent_size = 4 + [*.md] trim_trailing_whitespace = false diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f6a7ef..69f2dd58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project tries to adhere to [Semantic Versioning](https://semver.org/spe ## [Unreleased] ### Added +- add default schema, example, and tests - switch to different tiling service, add satellite base map layer, and toggle controls #25 - enable basic popup on results layer - results layer with customizable result views diff --git a/config/schemas.py b/config/schemas.py new file mode 100644 index 00000000..bbc81b56 --- /dev/null +++ b/config/schemas.py @@ -0,0 +1,25 @@ +import json +import os + +from config.settings.base import APPS_DIR + +SCHEMAS_DIR = APPS_DIR.path("schemas") +COMPONENTS_DIR = SCHEMAS_DIR.path("components") + +with open(os.path.join(SCHEMAS_DIR, "popup.schema.json"), "rb") as f: + POPUP_SCHEMA = json.loads(f.read()) + +with open(os.path.join(SCHEMAS_DIR, "popup.example.json"), "rb") as f: + POPUP_EXAMPLE = json.loads(f.read()) + +with open(os.path.join(COMPONENTS_DIR, "chart.schema.json"), "rb") as f: + CHART_SCHEMA = json.loads(f.read()) + +with open(os.path.join(COMPONENTS_DIR, "chart.example.json"), "rb") as f: + CHART_EXAMPLE = json.loads(f.read()) + +with open(os.path.join(COMPONENTS_DIR, "sources.schema.json"), "rb") as f: + SOURCES_SCHEMA = json.loads(f.read()) + +with open(os.path.join(COMPONENTS_DIR, "sources.example.json"), "rb") as f: + SOURCES_EXAMPLE = json.loads(f.read()) diff --git a/digiplan/conftest.py b/digiplan/conftest.py index b3099a08..0910280e 100644 --- a/digiplan/conftest.py +++ b/digiplan/conftest.py @@ -1,20 +1,12 @@ import pytest -from django.conf import settings from django.test import RequestFactory -from digiplan.users.tests.factories import UserFactory - @pytest.fixture(autouse=True) def media_storage(settings, tmpdir): settings.MEDIA_ROOT = tmpdir.strpath -@pytest.fixture -def user() -> settings.AUTH_USER_MODEL: - return UserFactory() - - @pytest.fixture def request_factory() -> RequestFactory: return RequestFactory() diff --git a/digiplan/schemas/components/chart.example.json b/digiplan/schemas/components/chart.example.json new file mode 100644 index 00000000..6d7ccb76 --- /dev/null +++ b/digiplan/schemas/components/chart.example.json @@ -0,0 +1,32 @@ +{ + "chartType": "bar", + "data": { + "definition": { + "key": { + "lookup": "year", + "name": "Year", + "type": "integer" + }, + "value": { + "lookup": "population", + "name": "Population", + "type": "integer" + } + }, + "series": [ + { + "key": 2022, + "value": 9311 + }, + { + "key": 2030, + "value": 9182 + }, + { + "key": 2045, + "value": 8903 + } + ] + }, + "title": "Population Forecast" +} diff --git a/digiplan/schemas/components/chart.schema.json b/digiplan/schemas/components/chart.schema.json new file mode 100644 index 00000000..65d8e3eb --- /dev/null +++ b/digiplan/schemas/components/chart.schema.json @@ -0,0 +1,172 @@ +{ + "$id": "https://wam.rl-institut.de/digiplan/schemas/components/chart.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Chart Schema. This schema is always referenced by other schemas and can't be standalone", + "properties": { + "chartType": { + "description": "The chart type that shall be used. If no chart shall be used, set value to (JSON type) null. Also look at following source for more info on chart types: https://echarts.apache.org/en/option.html#series", + "enum": [ + "line", + "bar", + "pie", + "scatter", + "effectScatter", + "radar", + "tree", + "treemap", + "sunburst", + "boxplot", + "candlestick", + "heatmap", + "map", + "parallel", + "lines", + "graph", + "sankey", + "funnel", + "gauge", + "pictorialBar", + "themeRiver", + "custom" + ], + "type": [ + "string", + "null" + ] + }, + "data": { + "additionalProperties": false, + "description": "Data object of chart with key value definitions and items list", + "properties": { + "definition": { + "additionalProperties": false, + "description": "Key-value definition for lookup and human-readable names", + "properties": { + "key": { + "additionalProperties": false, + "description": "Key lookup and name definition", + "properties": { + "lookup": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "description": "Possible types, defined by JSON Schema type names", + "enum": [ + "string", + "number", + "integer", + "object", + "array", + "boolean", + "null" + ], + "type": "string" + } + }, + "required": [ + "lookup", + "name", + "type" + ], + "type": "object" + }, + "value": { + "additionalProperties": false, + "description": "Value lookup and name definition", + "properties": { + "lookup": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "description": "Possible types, defined by JSON Schema type names", + "enum": [ + "string", + "number", + "integer", + "object", + "array", + "boolean", + "null" + ], + "type": "string" + } + }, + "required": [ + "lookup", + "name", + "type" + ], + "type": "object" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "series": { + "description": "Chart data series of key-value objects", + "items": { + "additionalProperties": false, + "description": "Key-value list item object", + "properties": { + "key": { + "type": [ + "string", + "number", + "integer", + "object", + "array", + "boolean", + "null" + ] + }, + "value": { + "type": [ + "string", + "number", + "integer", + "object", + "array", + "boolean", + "null" + ] + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "maxItems": 1000, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "definition", + "series" + ], + "type": "object" + }, + "title": { + "description": "Title of the chart", + "type": "string" + } + }, + "required": [ + "chartType", + "data", + "title" + ], + "type": "object" +} diff --git a/digiplan/schemas/components/sources.example.json b/digiplan/schemas/components/sources.example.json new file mode 100644 index 00000000..d1db9a98 --- /dev/null +++ b/digiplan/schemas/components/sources.example.json @@ -0,0 +1,14 @@ +[ + { + "name": "Bev\u00f6lkerungz\u00e4hlung Anhalt 2021", + "url": "https://wam.rl-institut.de/digiplan/sources#11" + }, + { + "name": "Bev\u00f6lkerungsprognose Anhalt 2030", + "url": "https://wam.rl-institut.de/digiplan/sources#12" + }, + { + "name": "Bev\u00f6lkerungsprognose Anhalt 2050", + "url": "https://wam.rl-institut.de/digiplan/sources#13" + } +] diff --git a/digiplan/schemas/components/sources.schema.json b/digiplan/schemas/components/sources.schema.json new file mode 100644 index 00000000..b39f2d64 --- /dev/null +++ b/digiplan/schemas/components/sources.schema.json @@ -0,0 +1,25 @@ +{ + "$id": "https://wam.rl-institut.de/digiplan/schemas/components/sources.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "List of data sources", + "items": { + "additionalProperties": false, + "description": "Name and URL (IRI) of data source", + "properties": { + "name": { + "type": "string" + }, + "url": { + "description": "identifier can be either URI or IRI (IRI is superset of URI). More info: https://json-schema.org/understanding-json-schema/reference/string.html#id7", + "format": "iri", + "type": "string" + } + }, + "required": [ + "name", + "url" + ], + "type": "object" + }, + "type": "array" +} diff --git a/digiplan/schemas/popup.example.json b/digiplan/schemas/popup.example.json new file mode 100644 index 00000000..204f50c6 --- /dev/null +++ b/digiplan/schemas/popup.example.json @@ -0,0 +1,52 @@ +{ + "chart": { + "chartType": "bar", + "data": { + "definition": { + "key": { + "lookup": "year", + "name": "Year", + "type": "integer" + }, + "value": { + "lookup": "population", + "name": "Population", + "type": "integer" + } + }, + "series": [ + { + "key": 2022, + "value": 9311 + }, + { + "key": 2030, + "value": 9182 + }, + { + "key": 2045, + "value": 8903 + } + ] + }, + "title": "Population Forecast" + }, + "description": "The population in 2022 of Z\u00f6rbig was 9,311 inhabitants. The entire ABW region had 370,190 inhabitants at that time. The following chart shows a forecast of the population development for the years 2022, 2030, and 2045.", + "id": 12, + "municipality": "Z\u00f6rbig", + "sources": [ + { + "name": "Bev\u00f6lkerungz\u00e4hlung Anhalt 2021", + "url": "https://wam.rl-institut.de/digiplan/sources#11" + }, + { + "name": "Bev\u00f6lkerungsprognose Anhalt 2030", + "url": "https://wam.rl-institut.de/digiplan/sources#12" + }, + { + "name": "Bev\u00f6lkerungsprognose Anhalt 2050", + "url": "https://wam.rl-institut.de/digiplan/sources#13" + } + ], + "title": "Population" +} diff --git a/digiplan/schemas/popup.schema.json b/digiplan/schemas/popup.schema.json new file mode 100644 index 00000000..110cf2e7 --- /dev/null +++ b/digiplan/schemas/popup.schema.json @@ -0,0 +1,42 @@ +{ + "$id": "https://wam.rl-institut.de/digiplan/schemas/popup.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "description": "Popup payload, contains popup components", + "properties": { + "chart": { + "$ref": "https://wam.rl-institut.de/digiplan/schemas/components/chart.schema.json" + }, + "description": { + "description": "User facing short description for informational purposes", + "maxLength": 280, + "type": "string" + }, + "id": { + "description": "ID of the entry", + "type": "integer" + }, + "municipality": { + "description": "Name of the municipality", + "maxLength": 50, + "type": "string" + }, + "sources": { + "$ref": "https://wam.rl-institut.de/digiplan/schemas/components/sources.schema.json" + }, + "title": { + "description": "Title", + "maxLength": 50, + "type": "string" + } + }, + "required": [ + "chart", + "description", + "id", + "municipality", + "sources", + "title" + ], + "type": "object" +} diff --git a/poetry.lock b/poetry.lock index 406ffc89..53aed142 100644 --- a/poetry.lock +++ b/poetry.lock @@ -95,7 +95,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "attrs" version = "22.1.0" description = "Classes Without Boilerplate" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -1081,6 +1081,22 @@ category = "dev" optional = false python-versions = ">=3.7" +[[package]] +name = "jsonschema" +version = "4.16.0" +description = "An implementation of JSON Schema validation for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + [[package]] name = "kombu" version = "5.2.4" @@ -1464,6 +1480,14 @@ python-versions = ">=3.6.8" [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyrsistent" +version = "0.18.1" +description = "Persistent/Functional/Immutable data structures" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "pytest" version = "6.2.5" @@ -1965,7 +1989,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "d0b69aa9da161875b2391380f17e22278123adc789ba80a18c905dd305686c42" +content-hash = "8002ee1bbfa62b9134386f6b04c6a545d51567a7b65649cf86b4395446f65188" [metadata.files] alabaster = [ @@ -2427,6 +2451,10 @@ jmespath = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +jsonschema = [ + {file = "jsonschema-4.16.0-py3-none-any.whl", hash = "sha256:9e74b8f9738d6a946d70705dc692b74b5429cd0960d58e79ffecfc43b2221eb9"}, + {file = "jsonschema-4.16.0.tar.gz", hash = "sha256:165059f076eff6971bae5b742fc029a7b4ef3f9bcf04c14e4776a7605de14b23"}, +] kombu = [ {file = "kombu-5.2.4-py3-none-any.whl", hash = "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4"}, {file = "kombu-5.2.4.tar.gz", hash = "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610"}, @@ -2735,6 +2763,29 @@ pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] +pyrsistent = [ + {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"}, + {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"}, + {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"}, + {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"}, +] pytest = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, @@ -2767,13 +2818,6 @@ PyYAML = [ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, diff --git a/pyproject.toml b/pyproject.toml index 484b1677..9798b523 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ djangorestframework-mvt = "^0.2.5" django-select2 = "^7.7.1" geojson = "^2.5.0" crispy-bootstrap5 = "^0.6" +jsonschema = "^4.16.0" [tool.poetry.dev-dependencies] Werkzeug = "^2.0.1" # https://github.com/pallets/werkzeug diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_schemas.py b/tests/test_schemas.py new file mode 100644 index 00000000..b603b80f --- /dev/null +++ b/tests/test_schemas.py @@ -0,0 +1,44 @@ +# pylint: disable=C0415 + + +# Components Schema Tests + + +def test_if_chart_example_validates(): + import jsonschema + + from config.schemas import CHART_SCHEMA # noqa: I001 + from config.schemas import CHART_EXAMPLE # noqa: I001 + + assert jsonschema.validate(CHART_EXAMPLE, CHART_SCHEMA) is None # noqa: S101 + + +def test_if_sources_example_validates(): + import jsonschema + + from config.schemas import SOURCES_SCHEMA # noqa: I001 + from config.schemas import SOURCES_EXAMPLE # noqa: I001 + + assert jsonschema.validate(SOURCES_EXAMPLE, SOURCES_SCHEMA) is None # noqa: S101 + + +# Popup Schema Test + + +def test_if_popup_example_validates(): + from jsonschema import Draft202012Validator, RefResolver + + from config.schemas import CHART_SCHEMA # noqa: I001 + from config.schemas import POPUP_SCHEMA # noqa: I001 + from config.schemas import POPUP_EXAMPLE # noqa: I001 + from config.schemas import SOURCES_SCHEMA # noqa: I001 + + schema_store = { + CHART_SCHEMA["$id"]: CHART_SCHEMA, + POPUP_SCHEMA["$id"]: POPUP_SCHEMA, + SOURCES_SCHEMA["$id"]: SOURCES_SCHEMA, + } + resolver = RefResolver.from_schema(POPUP_SCHEMA, store=schema_store) + validator = Draft202012Validator(POPUP_SCHEMA, resolver=resolver) + + assert validator.validate(POPUP_EXAMPLE) is None # noqa: S101