diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..582d90f --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# Unit test +.pytest_cache/ + +# Environments +.venv + +# Visual Studio Code +.vscode/ + +# Environment variables +.env \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..9315d09 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +repos: + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + exclude: package.lock.json + args: ["--exclude-lines", "\\s*\"image/png\": \".+\""] + + - repo: https://github.com/pre-commit/mirrors-isort + rev: v5.10.1 # Use the latest version + hooks: + - id: isort + + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.287 # Use the latest version + hooks: + - id: ruff + + - repo: https://github.com/psf/black + rev: 23.3.0 # Use the latest version + hooks: + - id: black diff --git a/README.md b/README.md index b40020f..dc40ff6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,97 @@ # metadata-schemas -Metadata JSON Schemas +This repository contains both the definitions of Metadata Schemas and a python library for creating schema objects with pydantic and Excel. -View documentation - https://worldbank.github.io/metadata-schemas/ +## Defining Metadata Schemas + +The schemas are defined in the JSON Schema format in the folder `schemas`. For more information you can view documentation at https://worldbank.github.io/metadata-schemas/ + +## Excel + +Excel sheets formatted for each metadata type are located in this repo in the excel_sheets folder. + +## Python library + +To install the library run + +```pip install metadataschemas``` + +### Creating a pydantic metadata object + +To create a timeseries metadata object run + +```python +from metadataschemas import timeseries_schema + +timeseries_metadata = timeseries_schema.TimeseriesSchema(idno='project_idno',series_description=timeseries_schema.SeriesDescription(idno='project_idno', name='project_name')) +``` + +Depending on your IDE, selecting `TimeseriesSchema` could show you what fields the schema contains and their corresponding object definitions. + +There are metadata objects for each of the following metadata types: + +| Metadata Type | Metadata Object | +|------------------|-------------------------------------------------| +| document | `document_schema.ScriptSchemaDraft` | +| geospatial | `geospatial_schema.GeospatialSchema` | +| script | `script_schema.ResearchProjectSchemaDraft` | +| series | `series_schema.Series` | +| survey | `microdata_schema.MicrodataSchema` | +| table | `table_schema.Model` | +| timeseries | `timeseries_schema.TimeseriesSchema` | +| timeseries_db | `timeseries_db_schema.TimeseriesDatabaseSchema` | +| video | `video_schema.Model` | + +### Python - Metadata Manager + +The Manager exists to be an interface with Excel and to lightly assist creating schemas. + +For Excel we can: + +1. Create blank Excel files formatted for a given metadata type +2. Write metadata objects to Excel +3. Read an appropriately formatted Excel file containing metadata into a pydantic metadata object + +To use it run: + +```python +from metadataschemas import MetadataManager + +mm = MetadataManager() + +filename = mm.write_metadata_outline_to_excel('timeseries') + +filename = mm.save_metadata_to_excel('timeseries', + object=timeseries_metadata) + +# Then after you have updated the metadata in the Excel file + +updated_timeseries_metadata = mm.read_metadata_from_excel(timeseries_metadata_filename) +``` + +Note that the Excel write and save functions do not currently support Geospatial metadata. + +The manager also offers a convenient way to get started creating metadata in pydantic by creating an empty pydantic object for a given metadata type which can then be updated as needed. + +```python +# list the supported metadata types +mm.metadata_type_names + +# get the pydantic class for a given metadata type +survey_type = mm.metadata_class_from_name("survey") + +# create an instantiated pydantic object and then fill in your data +survey_metadata = mm.type_to_outline(metadata_type="survey") +survey_metadata.repositoryid = "repository id" +survey_metadata.study_desc.title_statement.idno = "project_idno" +``` + + +## Updating Pydantic definitions and Excel sheets + +To update the pydantic schemas so that they match the latest json schemas run + + `python pydantic_schemas/generators/generate_pydantic_schemas.py` + +Then to update the Excel sheets run + + `python pydantic_schemas/generators/generate_excel_files.py` \ No newline at end of file diff --git a/excel_sheets/Document_metadata.xlsx b/excel_sheets/Document_metadata.xlsx new file mode 100644 index 0000000..d5237aa Binary files /dev/null and b/excel_sheets/Document_metadata.xlsx differ diff --git a/excel_sheets/Indicator_metadata.xlsx b/excel_sheets/Indicator_metadata.xlsx new file mode 100644 index 0000000..3fcbb68 Binary files /dev/null and b/excel_sheets/Indicator_metadata.xlsx differ diff --git a/excel_sheets/Indicators_db_metadata.xlsx b/excel_sheets/Indicators_db_metadata.xlsx new file mode 100644 index 0000000..9f2d6b8 Binary files /dev/null and b/excel_sheets/Indicators_db_metadata.xlsx differ diff --git a/excel_sheets/Microdata_metadata.xlsx b/excel_sheets/Microdata_metadata.xlsx new file mode 100644 index 0000000..173a2fd Binary files /dev/null and b/excel_sheets/Microdata_metadata.xlsx differ diff --git a/excel_sheets/Resource_metadata.xlsx b/excel_sheets/Resource_metadata.xlsx new file mode 100644 index 0000000..0aa789a Binary files /dev/null and b/excel_sheets/Resource_metadata.xlsx differ diff --git a/excel_sheets/Script_metadata.xlsx b/excel_sheets/Script_metadata.xlsx new file mode 100644 index 0000000..6029541 Binary files /dev/null and b/excel_sheets/Script_metadata.xlsx differ diff --git a/excel_sheets/Table_metadata.xlsx b/excel_sheets/Table_metadata.xlsx new file mode 100644 index 0000000..04da647 Binary files /dev/null and b/excel_sheets/Table_metadata.xlsx differ diff --git a/excel_sheets/Video_metadata.xlsx b/excel_sheets/Video_metadata.xlsx new file mode 100644 index 0000000..6a85ec7 Binary files /dev/null and b/excel_sheets/Video_metadata.xlsx differ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..bea39c6 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1724 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "argcomplete" +version = "3.5.0" +description = "Bash tab completion for argparse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "argcomplete-3.5.0-py3-none-any.whl", hash = "sha256:d4bcf3ff544f51e16e54228a7ac7f486ed70ebf2ecfe49a63a91171c76bf029b"}, + {file = "argcomplete-3.5.0.tar.gz", hash = "sha256:4349400469dccfb7950bb60334a680c58d88699bff6159df61251878dc6bf74b"}, +] + +[package.extras] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.6.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.2.2" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "datamodel-code-generator" +version = "0.25.9" +description = "Datamodel Code Generator" +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "datamodel_code_generator-0.25.9-py3-none-any.whl", hash = "sha256:9e0324233123d6e39a35bc0004771956935889a974aacfd7a0651de11d2219a9"}, + {file = "datamodel_code_generator-0.25.9.tar.gz", hash = "sha256:65ca9807d8edbd88a7f7931c10f4bc1c08bd9bbc5bb0508418a2b6a16590eb65"}, +] + +[package.dependencies] +argcomplete = ">=1.10,<4.0" +black = ">=19.10b0" +genson = ">=1.2.1,<2.0" +inflect = ">=4.1.0,<6.0" +isort = ">=4.3.21,<6.0" +jinja2 = ">=2.10.1,<4.0" +packaging = "*" +pydantic = [ + {version = ">=1.10.0,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.12\" and python_version < \"4.0\""}, + {version = ">=1.10.0,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, +] +pyyaml = ">=6.0.1" + +[package.extras] +debug = ["PySnooper (>=0.4.1,<2.0.0)"] +graphql = ["graphql-core (>=3.2.3,<4.0.0)"] +http = ["httpx"] +validation = ["openapi-spec-validator (>=0.2.8,<0.7.0)", "prance (>=0.18.2)"] + +[[package]] +name = "debugpy" +version = "1.8.2" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"}, + {file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"}, + {file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"}, + {file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"}, + {file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"}, + {file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"}, + {file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"}, + {file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"}, + {file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"}, + {file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"}, + {file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"}, + {file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"}, + {file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"}, + {file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"}, + {file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"}, + {file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"}, + {file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"}, + {file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"}, + {file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"}, + {file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"}, + {file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"}, + {file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "detect-secrets" +version = "1.5.0" +description = "Tool for detecting secrets in the codebase" +optional = false +python-versions = "*" +files = [ + {file = "detect_secrets-1.5.0-py3-none-any.whl", hash = "sha256:e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060"}, + {file = "detect_secrets-1.5.0.tar.gz", hash = "sha256:6bb46dcc553c10df51475641bb30fd69d25645cc12339e46c824c1e0c388898a"}, +] + +[package.dependencies] +pyyaml = "*" +requests = "*" + +[package.extras] +gibberish = ["gibberish-detector"] +word-list = ["pyahocorasick"] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "dnspython" +version = "2.6.1" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] + +[[package]] +name = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "genson" +version = "1.3.0" +description = "GenSON is a powerful, user-friendly JSON Schema generator." +optional = false +python-versions = "*" +files = [ + {file = "genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7"}, + {file = "genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37"}, +] + +[[package]] +name = "identify" +version = "2.5.36" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "inflect" +version = "5.6.2" +description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles; convert numbers to words" +optional = false +python-versions = ">=3.7" +files = [ + {file = "inflect-5.6.2-py3-none-any.whl", hash = "sha256:b45d91a4a28a4e617ff1821117439b06eaa86e2a4573154af0149e9be6687238"}, + {file = "inflect-5.6.2.tar.gz", hash = "sha256:aadc7ed73928f5e014129794bbac03058cca35d0a973a5fc4eb45c7fa26005f9"}, +] + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=24" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.26.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "ipython-8.26.0-py3-none-any.whl", hash = "sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff"}, + {file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5.13.0" +typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} + +[package.extras] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jupyter-client" +version = "8.6.2" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, + {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "numpy" +version = "2.1.0" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673"}, + {file = "numpy-2.1.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b"}, + {file = "numpy-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84"}, + {file = "numpy-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33"}, + {file = "numpy-2.1.0-cp310-cp310-win32.whl", hash = "sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211"}, + {file = "numpy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1"}, + {file = "numpy-2.1.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb"}, + {file = "numpy-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36"}, + {file = "numpy-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd"}, + {file = "numpy-2.1.0-cp311-cp311-win32.whl", hash = "sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e"}, + {file = "numpy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1"}, + {file = "numpy-2.1.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745"}, + {file = "numpy-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0"}, + {file = "numpy-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574"}, + {file = "numpy-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02"}, + {file = "numpy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33"}, + {file = "numpy-2.1.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195"}, + {file = "numpy-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1"}, + {file = "numpy-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62"}, + {file = "numpy-2.1.0-cp313-cp313-win32.whl", hash = "sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324"}, + {file = "numpy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300"}, + {file = "numpy-2.1.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd"}, + {file = "numpy-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a"}, + {file = "numpy-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575"}, + {file = "numpy-2.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2"}, + {file = "numpy-2.1.0.tar.gz", hash = "sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2"}, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, + {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pandas" +version = "2.2.2" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, + {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, + {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, + {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, + {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, + {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.7.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "prompt-toolkit" +version = "3.0.47" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "6.0.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "2.8.0" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.0-py3-none-any.whl", hash = "sha256:ead4f3a1e92386a734ca1411cb25d94147cf8778ed5be6b56749047676d6364e"}, + {file = "pydantic-2.8.0.tar.gz", hash = "sha256:d970ffb9d030b710795878940bd0489842c638e7252fc4a19c3ae2f7da4d6141"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} +pydantic-core = "2.20.0" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.0" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e9dcd7fb34f7bfb239b5fa420033642fff0ad676b765559c3737b91f664d4fa9"}, + {file = "pydantic_core-2.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:649a764d9b0da29816889424697b2a3746963ad36d3e0968784ceed6e40c6355"}, + {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7701df088d0b05f3460f7ba15aec81ac8b0fb5690367dfd072a6c38cf5b7fdb5"}, + {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab760f17c3e792225cdaef31ca23c0aea45c14ce80d8eff62503f86a5ab76bff"}, + {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb1ad5b4d73cde784cf64580166568074f5ccd2548d765e690546cff3d80937d"}, + {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b81ec2efc04fc1dbf400647d4357d64fb25543bae38d2d19787d69360aad21c9"}, + {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4a9732a5cad764ba37f3aa873dccb41b584f69c347a57323eda0930deec8e10"}, + {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6dc85b9e10cc21d9c1055f15684f76fa4facadddcb6cd63abab702eb93c98943"}, + {file = "pydantic_core-2.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:21d9f7e24f63fdc7118e6cc49defaab8c1d27570782f7e5256169d77498cf7c7"}, + {file = "pydantic_core-2.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b315685832ab9287e6124b5d74fc12dda31e6421d7f6b08525791452844bc2d"}, + {file = "pydantic_core-2.20.0-cp310-none-win32.whl", hash = "sha256:c3dc8ec8b87c7ad534c75b8855168a08a7036fdb9deeeed5705ba9410721c84d"}, + {file = "pydantic_core-2.20.0-cp310-none-win_amd64.whl", hash = "sha256:85770b4b37bb36ef93a6122601795231225641003e0318d23c6233c59b424279"}, + {file = "pydantic_core-2.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:58e251bb5a5998f7226dc90b0b753eeffa720bd66664eba51927c2a7a2d5f32c"}, + {file = "pydantic_core-2.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:78d584caac52c24240ef9ecd75de64c760bbd0e20dbf6973631815e3ef16ef8b"}, + {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5084ec9721f82bef5ff7c4d1ee65e1626783abb585f8c0993833490b63fe1792"}, + {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d0f52684868db7c218437d260e14d37948b094493f2646f22d3dda7229bbe3f"}, + {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1def125d59a87fe451212a72ab9ed34c118ff771e5473fef4f2f95d8ede26d75"}, + {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34480fd6778ab356abf1e9086a4ced95002a1e195e8d2fd182b0def9d944d11"}, + {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d42669d319db366cb567c3b444f43caa7ffb779bf9530692c6f244fc635a41eb"}, + {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53b06aea7a48919a254b32107647be9128c066aaa6ee6d5d08222325f25ef175"}, + {file = "pydantic_core-2.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1f038156b696a1c39d763b2080aeefa87ddb4162c10aa9fabfefffc3dd8180fa"}, + {file = "pydantic_core-2.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3f0f3a4a23717280a5ee3ac4fb1f81d6fde604c9ec5100f7f6f987716bb8c137"}, + {file = "pydantic_core-2.20.0-cp311-none-win32.whl", hash = "sha256:316fe7c3fec017affd916a0c83d6f1ec697cbbbdf1124769fa73328e7907cc2e"}, + {file = "pydantic_core-2.20.0-cp311-none-win_amd64.whl", hash = "sha256:2d06a7fa437f93782e3f32d739c3ec189f82fca74336c08255f9e20cea1ed378"}, + {file = "pydantic_core-2.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d6f8c49657f3eb7720ed4c9b26624063da14937fc94d1812f1e04a2204db3e17"}, + {file = "pydantic_core-2.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad1bd2f377f56fec11d5cfd0977c30061cd19f4fa199bf138b200ec0d5e27eeb"}, + {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed741183719a5271f97d93bbcc45ed64619fa38068aaa6e90027d1d17e30dc8d"}, + {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d82e5ed3a05f2dcb89c6ead2fd0dbff7ac09bc02c1b4028ece2d3a3854d049ce"}, + {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2ba34a099576234671f2e4274e5bc6813b22e28778c216d680eabd0db3f7dad"}, + {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:879ae6bb08a063b3e1b7ac8c860096d8fd6b48dd9b2690b7f2738b8c835e744b"}, + {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0eefc7633a04c0694340aad91fbfd1986fe1a1e0c63a22793ba40a18fcbdc8"}, + {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73deadd6fd8a23e2f40b412b3ac617a112143c8989a4fe265050fd91ba5c0608"}, + {file = "pydantic_core-2.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:35681445dc85446fb105943d81ae7569aa7e89de80d1ca4ac3229e05c311bdb1"}, + {file = "pydantic_core-2.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0f6dd3612a3b9f91f2e63924ea18a4476656c6d01843ca20a4c09e00422195af"}, + {file = "pydantic_core-2.20.0-cp312-none-win32.whl", hash = "sha256:7e37b6bb6e90c2b8412b06373c6978d9d81e7199a40e24a6ef480e8acdeaf918"}, + {file = "pydantic_core-2.20.0-cp312-none-win_amd64.whl", hash = "sha256:7d4df13d1c55e84351fab51383520b84f490740a9f1fec905362aa64590b7a5d"}, + {file = "pydantic_core-2.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d43e7ab3b65e4dc35a7612cfff7b0fd62dce5bc11a7cd198310b57f39847fd6c"}, + {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b6a24d7b5893392f2b8e3b7a0031ae3b14c6c1942a4615f0d8794fdeeefb08b"}, + {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2f13c3e955a087c3ec86f97661d9f72a76e221281b2262956af381224cfc243"}, + {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72432fd6e868c8d0a6849869e004b8bcae233a3c56383954c228316694920b38"}, + {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d70a8ff2d4953afb4cbe6211f17268ad29c0b47e73d3372f40e7775904bc28fc"}, + {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e49524917b8d3c2f42cd0d2df61178e08e50f5f029f9af1f402b3ee64574392"}, + {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4f0f71653b1c1bad0350bc0b4cc057ab87b438ff18fa6392533811ebd01439c"}, + {file = "pydantic_core-2.20.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:16197e6f4fdecb9892ed2436e507e44f0a1aa2cff3b9306d1c879ea2f9200997"}, + {file = "pydantic_core-2.20.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:763602504bf640b3ded3bba3f8ed8a1cc2fc6a87b8d55c1c5689f428c49c947e"}, + {file = "pydantic_core-2.20.0-cp313-none-win32.whl", hash = "sha256:a3f243f318bd9523277fa123b3163f4c005a3e8619d4b867064de02f287a564d"}, + {file = "pydantic_core-2.20.0-cp313-none-win_amd64.whl", hash = "sha256:03aceaf6a5adaad3bec2233edc5a7905026553916615888e53154807e404545c"}, + {file = "pydantic_core-2.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d6f2d8b8da1f03f577243b07bbdd3412eee3d37d1f2fd71d1513cbc76a8c1239"}, + {file = "pydantic_core-2.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a272785a226869416c6b3c1b7e450506152d3844207331f02f27173562c917e0"}, + {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efbb412d55a4ffe73963fed95c09ccb83647ec63b711c4b3752be10a56f0090b"}, + {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e4f46189d8740561b43655263a41aac75ff0388febcb2c9ec4f1b60a0ec12f3"}, + {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3df115f4a3c8c5e4d5acf067d399c6466d7e604fc9ee9acbe6f0c88a0c3cf"}, + {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a340d2bdebe819d08f605e9705ed551c3feb97e4fd71822d7147c1e4bdbb9508"}, + {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:616b9c2f882393d422ba11b40e72382fe975e806ad693095e9a3b67c59ea6150"}, + {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25c46bb2ff6084859bbcfdf4f1a63004b98e88b6d04053e8bf324e115398e9e7"}, + {file = "pydantic_core-2.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:23425eccef8f2c342f78d3a238c824623836c6c874d93c726673dbf7e56c78c0"}, + {file = "pydantic_core-2.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:52527e8f223ba29608d999d65b204676398009725007c9336651c2ec2d93cffc"}, + {file = "pydantic_core-2.20.0-cp38-none-win32.whl", hash = "sha256:1c3c5b7f70dd19a6845292b0775295ea81c61540f68671ae06bfe4421b3222c2"}, + {file = "pydantic_core-2.20.0-cp38-none-win_amd64.whl", hash = "sha256:8093473d7b9e908af1cef30025609afc8f5fd2a16ff07f97440fd911421e4432"}, + {file = "pydantic_core-2.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ee7785938e407418795e4399b2bf5b5f3cf6cf728077a7f26973220d58d885cf"}, + {file = "pydantic_core-2.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e75794883d635071cf6b4ed2a5d7a1e50672ab7a051454c76446ef1ebcdcc91"}, + {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:344e352c96e53b4f56b53d24728217c69399b8129c16789f70236083c6ceb2ac"}, + {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:978d4123ad1e605daf1ba5e01d4f235bcf7b6e340ef07e7122e8e9cfe3eb61ab"}, + {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c05eaf6c863781eb834ab41f5963604ab92855822a2062897958089d1335dad"}, + {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc7e43b4a528ffca8c9151b6a2ca34482c2fdc05e6aa24a84b7f475c896fc51d"}, + {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658287a29351166510ebbe0a75c373600cc4367a3d9337b964dada8d38bcc0f4"}, + {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1dacf660d6de692fe351e8c806e7efccf09ee5184865893afbe8e59be4920b4a"}, + {file = "pydantic_core-2.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e147fc6e27b9a487320d78515c5f29798b539179f7777018cedf51b7749e4f4"}, + {file = "pydantic_core-2.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c867230d715a3dd1d962c8d9bef0d3168994ed663e21bf748b6e3a529a129aab"}, + {file = "pydantic_core-2.20.0-cp39-none-win32.whl", hash = "sha256:22b813baf0dbf612752d8143a2dbf8e33ccb850656b7850e009bad2e101fc377"}, + {file = "pydantic_core-2.20.0-cp39-none-win_amd64.whl", hash = "sha256:3a7235b46c1bbe201f09b6f0f5e6c36b16bad3d0532a10493742f91fbdc8035f"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cafde15a6f7feaec2f570646e2ffc5b73412295d29134a29067e70740ec6ee20"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2aec8eeea0b08fd6bc2213d8e86811a07491849fd3d79955b62d83e32fa2ad5f"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:840200827984f1c4e114008abc2f5ede362d6e11ed0b5931681884dd41852ff1"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ea1d8b7df522e5ced34993c423c3bf3735c53df8b2a15688a2f03a7d678800"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5b8376a867047bf08910573deb95d3c8dfb976eb014ee24f3b5a61ccc5bee1b"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d08264b4460326cefacc179fc1411304d5af388a79910832835e6f641512358b"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7a3639011c2e8a9628466f616ed7fb413f30032b891898e10895a0a8b5857d6c"}, + {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05e83ce2f7eba29e627dd8066aa6c4c0269b2d4f889c0eba157233a353053cea"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:603a843fea76a595c8f661cd4da4d2281dff1e38c4a836a928eac1a2f8fe88e4"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac76f30d5d3454f4c28826d891fe74d25121a346c69523c9810ebba43f3b1cec"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e3b1d4b1b3f6082849f9b28427ef147a5b46a6132a3dbaf9ca1baa40c88609"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2761f71faed820e25ec62eacba670d1b5c2709bb131a19fcdbfbb09884593e5a"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a0586cddbf4380e24569b8a05f234e7305717cc8323f50114dfb2051fcbce2a3"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b8c46a8cf53e849eea7090f331ae2202cd0f1ceb090b00f5902c423bd1e11805"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b4a085bd04af7245e140d1b95619fe8abb445a3d7fdf219b3f80c940853268ef"}, + {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:116b326ac82c8b315e7348390f6d30bcfe6e688a7d3f1de50ff7bcc2042a23c2"}, + {file = "pydantic_core-2.20.0.tar.gz", hash = "sha256:366be8e64e0cb63d87cf79b4e1765c0703dd6313c729b22e7b9e378db6b96877"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.2.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "pyzmq" +version = "26.0.3" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, + {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, + {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, + {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, + {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, + {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, + {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, + {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "ruff" +version = "0.5.0" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"}, + {file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"}, + {file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"}, + {file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"}, + {file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"}, + {file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"}, + {file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"}, + {file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"}, + {file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "7671a941b5d68d34eb48386ca5fc6a447fbd50677549e35a46fe64869f94d6fb" diff --git a/pydantic_schemas/__init__.py b/pydantic_schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pydantic_schemas/document_schema.py b/pydantic_schemas/document_schema.py new file mode 100644 index 0000000..ba3c89f --- /dev/null +++ b/pydantic_schemas/document_schema.py @@ -0,0 +1,593 @@ +# generated by datamodel-codegen: +# filename: document-schema.json +# timestamp: 2024-09-13T19:00:20+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import Extra, Field + +from .utils.schema_base_model import SchemaBaseModel + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class MetadataInformation(SchemaBaseModel): + """ + Document description + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + production_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version: Optional[str] = Field( + None, description="Identify and describe the current version of the document", title="Document version" + ) + + +class TitleStatement(SchemaBaseModel): + """ + Study title + """ + + idno: str = Field( + ..., + description="The ID number of a dataset is a unique number that is used to identify a document.", + title="Unique user defined ID", + ) + title: str = Field(..., title="Title") + sub_title: Optional[str] = Field(None, title="Subtitle") + alternate_title: Optional[str] = Field(None, title="Abbreviation or Acronym") + translated_title: Optional[str] = Field(None, title="Translated Title") + + +class AuthorIdItem(SchemaBaseModel): + type: Optional[Any] = Field(None, description="Source of identifier, e.g. ORCID", title="Type") + id: Optional[Any] = Field( + None, description="Author's unique identifier for the corresponding source", title="Identifier" + ) + + +class Author(SchemaBaseModel): + first_name: Optional[str] = Field(None, title="First name") + initial: Optional[str] = Field(None, title="Initial") + last_name: Optional[str] = Field(None, title="Last name") + affiliation: Optional[str] = Field(None, title="Affiliation") + author_id: Optional[List[AuthorIdItem]] = Field( + None, + description="Unique identifier of an author, which may be provided by services like ORCID or other", + title="Author ID", + ) + full_name: Optional[str] = Field( + None, + description=( + "Full name of the author. This element to be used only when first or last name cannot be distinguished." + ), + title="Full name", + ) + + +class Editor(SchemaBaseModel): + first_name: Optional[str] = Field(None, title="First name") + initial: Optional[str] = Field(None, title="Initial") + last_name: Optional[str] = Field(None, title="Last name") + affiliation: Optional[str] = Field(None, title="Affiliation") + + +class Identifier(SchemaBaseModel): + type: Optional[str] = Field( + None, description="Type of identifier e.g. `doi`, `handle`, `other`", title="Identifier type" + ) + identifier: str = Field(..., title="Identifier") + + +class TocStructuredItem(SchemaBaseModel): + id: str = Field(..., title="ID or Number") + parent_id: Optional[str] = Field( + None, description="For sub levels, provide the ID of the parent TOC ID", title="Parent Identifier" + ) + name: str = Field(..., title="Title") + + +class Note(SchemaBaseModel): + note: Optional[str] = Field(None, title="Note") + + +class RefCountryItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Country name") + code: Optional[str] = Field(None, title="Country code") + + +class GeographicUnit(SchemaBaseModel): + name: str = Field( + ..., description="Name of the geographic unit e.g. 'World', 'Africa', 'Afghanistan'", title="Location name" + ) + code: Optional[str] = Field( + None, description="Code of the geographic unit (for countries, preferred = ISO3 code)", title="Location code" + ) + type: Optional[str] = Field( + None, description="Type of geographic unit e.g. country, state, region, province, town, etc", title="Type" + ) + + +class BboxItem(SchemaBaseModel): + west: Optional[str] = Field(None, title="West") + east: Optional[str] = Field(None, title="East") + south: Optional[str] = Field(None, title="South") + north: Optional[str] = Field(None, title="North") + + +class Language(SchemaBaseModel): + name: str = Field(..., title="Name") + code: Optional[str] = Field(None, title="Code") + + +class LicenseItem(SchemaBaseModel): + name: str = Field(..., title="License") + uri: Optional[str] = Field(None, title="URI") + + +class BibliographicCitationItem(SchemaBaseModel): + style: Optional[str] = Field(None, title="Style") + citation: str = Field(..., title="Citation") + + +class Translator(Editor): + pass + + +class Contributor(SchemaBaseModel): + first_name: Optional[str] = Field(None, title="First name") + initial: Optional[str] = Field(None, title="Initial") + last_name: Optional[str] = Field(None, title="Last name") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + contribution: Optional[str] = Field(None, title="Contribution") + + +class Contact(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + role: Optional[str] = Field(None, title="Role") + affiliation: Optional[str] = Field(None, title="Affiliation") + email: Optional[str] = Field(None, title="Email") + telephone: Optional[str] = Field(None, title="Telephone") + uri: Optional[str] = Field(None, title="URI") + + +class Source(SchemaBaseModel): + source_origin: Optional[str] = Field( + None, + description=( + "For historical materials, information about the origin(s) of the sources and the rules followed in" + " establishing the sources should be specified. May not be relevant to survey data. " + ), + title="Origin of Source", + ) + source_char: Optional[str] = Field( + None, + description="Assessment of characteristics and quality of source material. May not be relevant to survey data.", + title="Characteristics of Source Noted", + ) + source_doc: Optional[str] = Field( + None, description="Documentation and Access to Sources", title="Source documentation" + ) + + +class DataSource(SchemaBaseModel): + name: str = Field(..., title="Dataset name") + uri: Optional[str] = Field(None, description="Link to the dataset", title="URI") + note: Optional[str] = Field(None, title="Note") + + +class Theme(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Name") + parent_id: Optional[str] = Field(None, title="Parent Identifier") + vocabulary: Optional[str] = Field(None, description="Name of the controlled vocabulary", title="Vocabulary") + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the theme is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Topic(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Topic") + parent_id: Optional[str] = Field( + None, description="For subtopics, provide the ID of the parent topic", title="Parent topic Identifier" + ) + vocabulary: Optional[str] = Field( + None, description="Name of the controlled vocabulary, if the topic is from a taxonomy.", title="Vocabulary" + ) + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the topic is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Discipline(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Discipline title or name") + parent_id: Optional[str] = Field(None, description="Parent discipline ID", title="Parent discipline Identifier") + vocabulary: Optional[str] = Field(None, description="Vocabulary", title="Vocabulary") + uri: Optional[str] = Field(None, description="Website link", title="URI") + + +class Type(Enum): + isPartOf = "isPartOf" + hasPart = "hasPart" + isVersionOf = "isVersionOf" + isFormatOf = "isFormatOf" + hasFormat = "hasFormat" + references = "references" + isReferencedBy = "isReferencedBy" + isBasedOn = "isBasedOn" + isBasisFor = "isBasisFor" + requires = "requires" + isRequiredBy = "isRequiredBy" + + +class Relation(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + type: Optional[Type] = Field(None, title="Type") + + +class Link(SchemaBaseModel): + uri: str = Field(..., title="URI") + description: Optional[str] = Field(None, title="Description") + + +class Reproducibility(SchemaBaseModel): + statement: Optional[str] = Field(None, title="Statement") + links: Optional[List[Link]] = Field(None, title="Link") + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class ModelInfoItem(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + author: Optional[str] = Field(None, title="Author") + version: Optional[str] = Field(None, title="Version") + model_id: Optional[str] = Field(None, title="Model Identifier") + nb_topics: Optional[float] = Field(None, title="Number of topics") + description: Optional[str] = Field(None, title="Description") + corpus: Optional[str] = Field(None, title="Corpus name") + uri: Optional[str] = Field(None, title="URI") + + +class TopicWord(SchemaBaseModel): + word: Optional[str] = Field(None, title="Word") + word_weight: Optional[float] = Field(None, title="Word weight") + + +class TopicDescriptionItem(SchemaBaseModel): + topic_id: Optional[Union[int, str]] = Field(None, title="Topic identifier") + topic_score: Optional[Union[float, str]] = Field(None, title="Topic score") + topic_label: Optional[str] = Field(None, title="Topic label") + topic_words: Optional[List[TopicWord]] = Field(None, description="Words", title="Topic words") + + +class LdaTopic(SchemaBaseModel): + class Config: + extra = Extra.forbid + + model_info: Optional[List[ModelInfoItem]] = Field(None, title="Model information") + topic_description: Optional[List[TopicDescriptionItem]] = Field(None, title="Topic information") + + +class Embedding(SchemaBaseModel): + id: str = Field(..., title="Vector Model ID") + description: Optional[str] = Field(None, title="Vector Model Description") + date: Optional[str] = Field(None, title="Date (YYYY-MM-DD)") + vector: Union[Dict[str, Any], List[Any]] = Field(..., title="Vector") + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class KeywordItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + vocabulary: Optional[str] = Field(None, title="Vocabulary name") + uri: Optional[str] = Field(None, title="Vocabulary URI") + + +class DocumentDescription(SchemaBaseModel): + """ + Document Description + """ + + class Config: + extra = Extra.forbid + + title_statement: TitleStatement = Field(..., description="Study title") + authors: Optional[List[Author]] = Field(None, description="Authors", title="Authors") + editors: Optional[List[Editor]] = Field(None, description="Editors", title="Editors") + date_created: Optional[str] = Field(None, description="Date of creation", title="Date created") + date_available: Optional[str] = Field( + None, + description="Date (often a range) that the resource will become or did become available.", + title="Date available", + ) + date_modified: Optional[str] = Field( + None, description="Date on which the resource was changed.", title="Date last modified" + ) + date_published: Optional[str] = Field( + None, description="Date on which document was published.", title="Date published" + ) + identifiers: Optional[List[Identifier]] = Field(None, description="Other identifiers", title="Other identifiers") + type: Optional[str] = Field( + None, + description=( + "Valid values include - `article`, `book`, `booklet`, `collection`, `conference`, `inbook`, `incollection`," + " `inproceeding`,`manual`, `masterthesis`, `patent`, `phdthesis`, `proceedings`, `techreport`," + " `working-paper`, `website`, `other` " + ), + title="Resource type", + ) + status: Optional[str] = Field( + None, + description=( + "Status of the document - e.g. `Draft`, `Draft released for comment`, `Final draft released for comment`," + " `Final` " + ), + title="Status", + ) + description: Optional[str] = Field( + None, description="An account of the content of the resource.", title="Description" + ) + toc: Optional[str] = Field(None, description="Table of contents", title="Table of contents") + toc_structured: Optional[List[TocStructuredItem]] = Field( + None, description="Table of contents", title="Table of contents" + ) + abstract: Optional[str] = Field(None, description="A summary of the content", title="Abstract") + notes: Optional[List[Note]] = Field(None, title="Notes") + scope: Optional[str] = Field( + None, + description=( + "The extent or scope of the content of the resource. This fields maps to Dublin Core's coverage field." + ), + title="Scope", + ) + ref_country: Optional[List[RefCountryItem]] = Field(None, title="Reference country") + geographic_units: Optional[List[GeographicUnit]] = Field( + None, + description=( + "List of geographic locations (regions, countries, states, provinces, etc.) describing the geographic" + " coverahe of the research project." + ), + title="Geographic locations", + ) + bbox: Optional[List[BboxItem]] = Field(None, title="Geographic bounding box") + spatial_coverage: Optional[str] = Field( + None, description="The spatial extent or scope of the content of the resource.", title="Spatial coverage" + ) + temporal_coverage: Optional[str] = Field( + None, description="The temporal extent or scope of the content of the resource.", title="Temporal coverage" + ) + publication_frequency: Optional[str] = Field( + None, + description=( + "Current stated publication frequency of either an item or an update to an item. Dates are included when" + " the beginning date of the current frequency is not the same as the beginning date of publication." + ), + title="Publication frequency", + ) + languages: Optional[List[Language]] = Field( + None, description="Documentation language e.g. English, French, etc.", title="Language" + ) + license: Optional[List[LicenseItem]] = Field(None, title="License") + bibliographic_citation: Optional[List[BibliographicCitationItem]] = Field( + None, description="A bibliographic reference for the resource.", title="Bibliographic citation" + ) + chapter: Optional[str] = Field(None, description="A chapter or section number", title="Chapter number") + edition: Optional[str] = Field(None, description="The edition of a book", title="Edition") + institution: Optional[str] = Field( + None, description="The sponsoring institution of a document.", title="Institution" + ) + journal: Optional[str] = Field(None, description="Name of the Journal", title="Journal name") + volume: Optional[str] = Field(None, description="Volume number", title="Volume number") + number: Optional[str] = Field( + None, + description=( + "The number of a journal, magazine, technical report, or of a work in a series. An issue of a journal or" + " magazine is usually identified by its volume and number; the organization that issues a technical report" + " usually gives it a number; and sometimes books are given numbers in a named series." + ), + title="Number", + ) + pages: Optional[str] = Field( + None, + description="One or more page numbers or ranges of number, such as 37--42, or 7,53,82--94", + title="Page numbers", + ) + series: Optional[str] = Field( + None, + description=( + "The name given to a series or set of books. When citing an entire book, the title field gives its title" + " and the optional series field gives the name of a series in which the book was published." + ), + title="Series name", + ) + publisher: Optional[str] = Field( + None, description="Entity responsible for making the resource available", title="Publisher" + ) + publisher_address: Optional[str] = Field( + None, + description=( + "For major publishing houses, just the city is given. For small publishers, you can help the reader by" + " giving the complete address." + ), + title="Publisher's address", + ) + annote: Optional[str] = Field( + None, + description=( + "For annotation, element will not be used by standard bibliography styles like the MLA, APA or Chicago, but" + " may be used by others that produce an annotated bibliography." + ), + title="Annotation", + ) + booktitle: Optional[str] = Field( + None, description="Title of a book, part of which is being cited", title="Book title" + ) + crossref: Optional[str] = Field( + None, description="The database key of the entry being cross referenced", title="Cross reference" + ) + howpublished: Optional[str] = Field( + None, + description=( + "The element is used to store the notice for unusual publications. The first word should be capitalized." + " For example, `WebPage`, or `Distributed at the local tourist office`" + ), + title="Store the notice for unusual publications", + ) + key: Optional[str] = Field( + None, + description=( + "A key is a field used for alphabetizing, cross referencing, and creating a label when the `author'" + " information is missing" + ), + title="Key", + ) + organization: Optional[str] = Field( + None, description="The organization that sponsors a conference or that publishes a manual", title="Organization" + ) + url: Optional[List[str]] = Field(None, description="URL of the document, preferably a permanent URL", title="URL") + translators: Optional[List[Translator]] = Field(None, description="Translators", title="Translators") + contributors: Optional[List[Contributor]] = Field(None, description="Contributors", title="Contributors") + acknowledgement_statement: Optional[str] = Field( + None, description="Acknowledgement statement", title="Acknowledgement statement" + ) + contacts: Optional[List[Contact]] = Field(None, description="Contacts", title="Contacts") + rights: Optional[str] = Field( + None, description="Information about rights held in and over the resource.", title="Rights" + ) + copyright: Optional[str] = Field( + None, + description=( + "Statement and identifier indicating the legal ownership and rights regarding use and re-use of all or part" + " of the resource." + ), + title="Copyright", + ) + usage_terms: Optional[str] = Field( + None, description="Terms Governing Use and Reproduction", title="Terms governing use and reproduction" + ) + disclaimer: Optional[str] = Field(None, description="Disclaimer", title="Disclaimer") + security_classification: Optional[str] = Field( + None, + description=( + "Specifics pertaining to the security classification associated with the document, title, abstract," + " contents note, and/or the author. In addition, it can contain handling instructions and external" + " dissemination information pertaining to the dissemination of the document, title, abstract, contents" + " note, and author." + ), + title="Security classification control", + ) + access_restrictions: Optional[str] = Field( + None, + description="Information about restrictions imposed on access to the described materials.", + title="Restrictions on Access", + ) + sources: Optional[List[Source]] = Field( + None, + description=( + "Description of sources used. The element is nestable so that the sources statement might encompass a" + " series of discrete source statements, each of which could contain the facts about an individual source. " + ), + title="Sources", + ) + data_sources: Optional[List[DataSource]] = Field( + None, + description=( + "Used to list the book(s), article(s), serial(s), and/or machine-readable data file(s)--if any--that served" + " as the source(s) of the data collection." + ), + title="Data Sources", + ) + keywords: Optional[List[KeywordItem]] = Field(None, description="Keywords", title="Keywords") + themes: Optional[List[Theme]] = Field(None, description="Themes") + topics: Optional[List[Topic]] = Field( + None, + description="Topics covered by the table (ideally, the list of topics will be a controlled vocabulary)", + title="Topics", + ) + disciplines: Optional[List[Discipline]] = Field( + None, + description="Disciplines e.g. `Social sciences, economics`, `Natural sciences, biology`", + title="Disciplines", + ) + audience: Optional[str] = Field( + None, description="A category of user for whom the resource is intended.", title="Audience" + ) + mandate: Optional[str] = Field( + None, description="A category of user for whom the resource is intended.", title="Audience" + ) + pricing: Optional[str] = Field( + None, + description="Current price of an item or the special export price of an item in any currency.", + title="Pricing", + ) + relations: Optional[List[Relation]] = Field(None, description="Related documents", title="Document relations") + reproducibility: Optional[Reproducibility] = Field(None, title="Reproducibility") + + +class ScriptSchemaDraft(SchemaBaseModel): + """ + Schema for Document data type + """ + + idno: Optional[str] = Field(None, description="Project unique identifier", title="Project unique identifier") + metadata_information: Optional[MetadataInformation] = Field( + None, description="Document description", title="Document metadata information" + ) + document_description: DocumentDescription = Field( + ..., description="Document Description", title="Document Description" + ) + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags") + lda_topics: Optional[List[LdaTopic]] = Field(None, description="LDA topics", title="LDA topics") + embeddings: Optional[List[Embedding]] = Field(None, description="Word embeddings", title="Word embeddings") + additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata") diff --git a/pydantic_schemas/generators/generate_excel_files.py b/pydantic_schemas/generators/generate_excel_files.py new file mode 100644 index 0000000..00da0dd --- /dev/null +++ b/pydantic_schemas/generators/generate_excel_files.py @@ -0,0 +1,14 @@ +import os + +from pydantic_schemas.metadata_manager import MetadataManager + +metadata_manager = MetadataManager() + +for metadata_name in metadata_manager.metadata_type_names: + if metadata_name in ["image", "geospatial"]: + continue + filename = f"excel_sheets/{metadata_name.capitalize()}_metadata.xlsx" + print(f"Writing {metadata_name} outline to {filename}") + if os.path.exists(filename): + os.remove(filename) + metadata_manager.write_metadata_outline_to_excel(metadata_name_or_class=metadata_name, filename=filename) diff --git a/pydantic_schemas/generators/generate_pydantic_schemas.py b/pydantic_schemas/generators/generate_pydantic_schemas.py new file mode 100644 index 0000000..6344806 --- /dev/null +++ b/pydantic_schemas/generators/generate_pydantic_schemas.py @@ -0,0 +1,62 @@ +import os +from subprocess import run + +SCHEMA_DIR = "schemas" +OUTPUT_DIR = os.path.join("pydantic_schemas") +PYTHON_VERSION = "3.11" +BASE_CLASS = ".utils.schema_base_model.SchemaBaseModel" +# INPUTS = [ +# "document-schema.json", +# "geospatial-schema.json", +# "image-schema.json", +# "microdata-schema.json", +# "resource-schema.json", +# "script-schema.json", +# "table-schema.json", +# "timeseries-db-schema.json", +# "timeseries-schema.json", +# "video-schema.json", +# ] + +INPUTS_TO_OUTPUTS = { + "document-schema.json": "document_schema.py", + "geospatial-schema.json": "geospatial_schema.py", + "image-schema.json": "image_schema.py", + "microdata-schema.json": "microdata_schema.py", + "resource-schema.json": "resource_schema.py", + "script-schema.json": "script_schema.py", + "table-schema.json": "table_schema.py", + "timeseries-db-schema.json": "indicators_db_schema.py", + "timeseries-schema.json": "indicator_schema.py", + "video-schema.json": "video_schema.py", +} + + +if not os.path.exists(OUTPUT_DIR): + os.makedirs(OUTPUT_DIR) + +for input_file, output_file in INPUTS_TO_OUTPUTS.items(): + print(f"Generating pydantic schema for {input_file}") + input_path = os.path.join(SCHEMA_DIR, input_file) + # output_file = os.path.splitext(input_file)[0] + ".py" + output_path = os.path.join(OUTPUT_DIR, output_file).replace("-", "_") + run( + [ + "datamodel-codegen", + "--input", + input_path, + "--input-file-type", + "jsonschema", + "--reuse-model", + "--use-schema-description", + "--target-python-version", + PYTHON_VERSION, + "--use-double-quotes", + "--wrap-string-literal", + "--collapse-root-models", + "--base-class", + BASE_CLASS, + "--output", + output_path, + ] + ) diff --git a/pydantic_schemas/geospatial_schema.py b/pydantic_schemas/geospatial_schema.py new file mode 100644 index 0000000..dbe46aa --- /dev/null +++ b/pydantic_schemas/geospatial_schema.py @@ -0,0 +1,1556 @@ +# generated by datamodel-codegen: +# filename: geospatial-schema.json +# timestamp: 2024-09-13T19:00:22+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import Extra, Field, confloat + +from .utils.schema_base_model import SchemaBaseModel + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class MetadataInformation(SchemaBaseModel): + """ + Document description + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + production_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version: Optional[str] = Field( + None, description="Identify and describe the current version of the document", title="Document version" + ) + + +class GeometricObject(SchemaBaseModel): + """ + Definition of the geometric objects including the geometry type and count + """ + + geometricObjectType: Optional[str] = Field( + None, + description=( + "Identification of the objects used to represent features in the vector spatial dataset. Codelist value" + " according to the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_GeometricObjectTypeCode)" + " GeometricObjectType codelist. Possible values: {`complex`, `composite`, `curve`, `point`, `solid`," + " `surface`}" + ), + title="Geometric Object Type", + ) + geometricObjectCount: Optional[int] = Field( + None, description="Number of geometric objects available for the resource", title="Geometric Object count" + ) + + +class VectorSpatialRepresentation(SchemaBaseModel): + """ + Vector Resource spatial representation - Spatial representation information for the dataset (resource). Best practice is to include metadata for spatial representation if the described resource is a georeferenced vector dataset. + """ + + topologyLevel: Optional[str] = Field( + None, + description=( + "Topology level associated to the vector resource. Codelist value according to the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_TopologyLevelCode) TopologyLevel" + " codelist. Possible values: {`geometryOnly`, `topology1D`, `planarGraph`, `fullPlanarGraph`," + " `surfaceGraph`, `fullSurfaceGraph`, `topology3D`, `fullTopology3D`, `abstract`}" + ), + title="Topology Level", + ) + geometricObjects: Optional[List[GeometricObject]] = Field( + None, + description="Definition(s) of the geometric objects including the geometry type and count", + title="Geometric objects definition(s)", + ) + + +class Resolution(SchemaBaseModel): + """ + Resolution associated to the dimension. The resolution is handled as 'measure' which could be either a length, distance [special measure of length), angle or scale. + """ + + uom: Optional[str] = Field(None, description="Unit considered for the resolution measure", title="Unit Of Measure") + + +class AxisDimensionProperty(SchemaBaseModel): + dimensionName: Optional[str] = Field( + None, + description=( + "name type of the dimension. Codelist value according to the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_DimensionNameTypeCode)" + " DimensionNameType codelist. Possible values: {`row`, `column`, `vertical`, `track`, `crossTrack`, `line`," + " `sample`, `time`}" + ), + title="Dimension name type", + ) + dimensionSize: Optional[int] = Field(None, description="Size of the dimension", title="Dimension size") + resolution: Optional[Resolution] = Field( + None, + description=( + "Resolution associated to the dimension. The resolution is handled as 'measure' which could be either a" + " length, distance [special measure of length), angle or scale." + ), + title="Dimension resolution", + ) + + +class GridSpatialRepresentation(SchemaBaseModel): + """ + Grid Resource spatial representation - Spatial representation information for the dataset (resource). Best practice is to include metadata for spatial representation if the described resource is a georeferenced gridded / raster dataset. + """ + + numberOfDimensions: Optional[int] = Field( + None, description="Number of dimensions in the grid", title="Number of dimensions" + ) + axisDimensionProperties: Optional[List[AxisDimensionProperty]] = Field( + None, description="Properties of the axis dimensions", title="Axis dimension properties" + ) + cellGeometry: Optional[str] = Field( + None, + description=( + "Type of geometry used for the grid cells. Codelist value according to the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_CellGeometryCode)" + " CellGeometryCode codelist. Possible values: {`point`, `area`, `voxel`, `stratum`}" + ), + title="Cell geometry", + ) + transformationParameterAvailability: Optional[bool] = Field( + None, + description="Indicates whether grid transformation parameters are available", + title="Transformation Parameter Availability", + ) + + +class SpatialRepresentationInfoItem(SchemaBaseModel): + vectorSpatialRepresentation: Optional[VectorSpatialRepresentation] = Field( + None, + description=( + "Vector Resource spatial representation - Spatial representation information for the dataset (resource)." + " Best practice is to include metadata for spatial representation if the described resource is a" + " georeferenced vector dataset." + ), + ) + gridSpatialRepresentation: Optional[GridSpatialRepresentation] = Field( + None, + description=( + "Grid Resource spatial representation - Spatial representation information for the dataset (resource)." + " Best practice is to include metadata for spatial representation if the described resource is a" + " georeferenced gridded / raster dataset." + ), + ) + + +class AssociationType(SchemaBaseModel): + """ + Type of association between the dataset resource and the aggregate Resource + """ + + codeListValue: Optional[str] = Field( + None, + title=( + "Association type, eg. 'isComposedOf'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_AssociationTypeCode)" + " AssociationType codelist. Suggested values: {crossReference, largerWorkCitation, partOfSeamlessDatabase," + " source, stereoMate, isComposedOf, collectiveTitle, series, dependency, revisionOf}" + ), + ) + codeList: Optional[str] = Field( + None, + title=( + "Codelist used for association types. Recommended URI:" + " http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_AssociationTypeCode" + ), + ) + codeSpace: Optional[str] = Field( + None, title="Codespace of the association types codelist. Recommended value: ISOTC211/19115" + ) + + +class InitiativeType(SchemaBaseModel): + """ + Type of initative behind the aggregate Resource + """ + + codeListValue: Optional[str] = Field( + None, + title=( + "Initiative type, eg. 'collection'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_InitiativeTypeCode)" + " InitiativeType codelist. Suggested values: {campaign, collection, dataDictionary, exercise, experiment," + " investigation, mission, sensor, operation, platform, process, program, project, sciencePaper, study," + " task, trial, userGuide}" + ), + ) + codeList: Optional[str] = Field( + None, + title=( + "Codelist used for initiative types. Recommended URI:" + " http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DS_InitiativeTypeCode" + ), + ) + codeSpace: Optional[str] = Field( + None, title="Codespace of the initiative types codelist. Recommended value: ISOTC211/19115" + ) + + +class AggregationInfo(SchemaBaseModel): + """ + Identification of aggregate that encompasses the described resource, eg. data collection + """ + + aggregateDataSetName: Optional[str] = Field(None, title="Name of the Aggregate dataset") + aggregateDataSetIdentifier: Optional[str] = Field(None, title="Identifier of the Aggregate dataset") + associationType: Optional[AssociationType] = Field( + None, + description="Type of association between the dataset resource and the aggregate Resource", + title="Association type", + ) + initiativeType: Optional[InitiativeType] = Field( + None, description="Type of initative behind the aggregate Resource", title="Initiative type" + ) + + +class GeographicBoundingBox(SchemaBaseModel): + westBoundLongitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="West") + eastBoundLongitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="East") + southBoundLatitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="South") + northBoundLatitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="North") + + +class Geohash(SchemaBaseModel): + geohash: Optional[str] = Field(None, title="Geohash") + note: Optional[str] = Field(None, title="Note") + + +class Ring(Enum): + exterior = "exterior" + interior = "interior" + + +class Type(Enum): + Point = "Point" + LineString = "LineString" + Polygon = "Polygon" + + +class PolygonItem(SchemaBaseModel): + ring: Optional[Ring] = Field(None, title="Ring boundary") + type: Optional[Type] = Field(None, title="Type") + coordinates: Optional[List[List[float]]] = Field(None, title="Coordinates") + + +class GeographicBoundingPolygon(SchemaBaseModel): + """ + Geographic Bounding Polygon + """ + + id: Optional[str] = Field(None, title="Identifier") + polygon: Optional[List[PolygonItem]] = Field(None, title="Polygon") + + +class GeographicElementItem(SchemaBaseModel): + geographicBoundingBox: Optional[GeographicBoundingBox] = Field(None, title="Geographic Bounding Box") + geohash: Optional[Geohash] = Field(None, title="Geohash") + geographicDescription: Optional[str] = Field(None, title="Geographic description identifier") + geographicBoundingPolygon: Optional[GeographicBoundingPolygon] = Field( + None, description="Geographic Bounding Polygon", title="Geographic Bounding Polygon" + ) + + +class TemporalElementExtentItem(SchemaBaseModel): + beginPosition: Optional[str] = Field( + None, + description=( + "Begin time position. Requires an extended ISO 8601 formatted combined UTC date and time string" + " (2009-11-17T10:00:00)" + ), + title="Begin time position", + ) + endPosition: Optional[str] = Field( + None, + description=( + "End time position. Requires an extended ISO 8601 formatted combined UTC date and time string" + " (2009-11-17T10:00:00)" + ), + title="End time position", + ) + + +class VerticalElementItem(SchemaBaseModel): + minimumValue: Optional[float] = Field(None, title="Minimum vertical value") + maximumValue: Optional[float] = Field(None, title="Maximum vertical value") + verticalCRS: Optional[Any] = Field( + None, + description="Vertical coordinate reference system used (as defined in the ISO 19136 / GML standard", + title="Vertical CRS", + ) + + +class Extent(SchemaBaseModel): + """ + Defines the spatial (horizontal and vertical) and temporal region to which the content of the resource applies. + """ + + geographicElement: Optional[List[GeographicElementItem]] = Field( + None, description="Geographic extent(s)", title="Geographic extent(s)" + ) + temporalElementExtent: Optional[List[TemporalElementExtentItem]] = Field(None, title="Temporal extent(s)") + verticalElement: Optional[List[VerticalElementItem]] = Field(None, title="Vertical extent(s)") + + +class SpatialResolutionItem(SchemaBaseModel): + uom: Optional[str] = Field(None, description="Unit considered for the resolution measure", title="Unit Of Measure") + value: Optional[float] = Field(None, description="Value", title="Value") + + +class AccessProperties(SchemaBaseModel): + fees: Optional[str] = Field(None, description="Eventual fees associated with the service", title="Fees") + plannedAvailableDateTime: Optional[str] = Field( + None, + description=( + "Date and time when the metadata record was created or updated. Requires an extended ISO 8601 formatted" + " combined UTC date and time string (2009-11-17T10:00:00)" + ), + title="Service availability Date Stamp", + ) + orderingInstructions: Optional[str] = Field( + None, description="Eventual instructions for the ordering", title="Ordering instructions" + ) + turnaround: Optional[str] = Field(None, description="Turnaround", title="Turnaround") + + +class CoupledResourceItem(SchemaBaseModel): + operationName: Optional[str] = Field(None, title="Operation name") + identifier: Optional[str] = Field(None, title="Identifier of the coupled resource") + + +class CouplingType(SchemaBaseModel): + """ + Coupling type + """ + + codeListValue: Optional[str] = Field( + None, + title=( + "Coupling type, eg. 'loose'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#SV_CouplingTypeCode) CouplingType" + " codelist. Suggested values: {loose, mixed, tight}" + ), + ) + codeList: Optional[str] = Field( + None, + title=( + "Codelist used for coupling types. Recommended URI:" + " http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#SV_CouplingTypeCode" + ), + ) + codeSpace: Optional[str] = Field( + None, title="Codespace of the coupling types codelist. Recommended value: ISOTC211/19119" + ) + + +class OperatesOnItem(SchemaBaseModel): + """ + Operates On relationship + """ + + uuidref: Optional[str] = Field(None, title="Unique dataset identifier within the same catalogue") + + +class ContentType(SchemaBaseModel): + """ + Type of coverage content + """ + + codeListValue: Optional[str] = Field( + None, + title=( + "Type of coverage content, eg. 'image'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_CoverageContentTypeCode) Coverage" + " content type codelist. Suggested values: {image, thematicClassification, physicalMeasurement," + " auxillaryInformation, qualityInformation, referenceInformation, modelResult, coordinate, auxilliaryData}" + ), + ) + codeList: Optional[str] = Field( + None, + title=( + "Codelist used for coverage content types. Recommended URI:" + " http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_CoverageContentTypeCode" + ), + ) + codeSpace: Optional[str] = Field( + None, title="Codespace of the coverage content types codelist. Recommended value: ISOTC211/19115" + ) + + +class DimensionItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + type: Optional[str] = Field(None, title="Dimension type") + + +class CoverageDescription(SchemaBaseModel): + """ + description of the coverage (grid/raster), ie. the definition of the grid/raster data structure + """ + + contentType: Optional[ContentType] = Field( + None, description="Type of coverage content", title="Coverage content type" + ) + dimension: Optional[List[DimensionItem]] = Field(None, title="Coverage dimensions") + + +class Cardinality(SchemaBaseModel): + """ + Definition of the member type cardinality + """ + + lower: Optional[int] = Field(None, description="Lower cardinality range value", title="Lower cardinality") + upper: Optional[int] = Field(None, description="Upper cardinality range value", title="Upper cardinality") + + +class ListedValueItem(SchemaBaseModel): + label: Optional[str] = Field(None, description="a label for the value", title="Value label") + code: Optional[str] = Field(None, description="a code for the value", title="Value code") + definition: Optional[str] = Field(None, description="a definition for the value", title="Value definition") + + +class CarrierOfCharacteristic(SchemaBaseModel): + memberName: Optional[str] = Field( + None, description="Name of the property member of the feature type", title="Member name" + ) + definition: Optional[str] = Field( + None, description="Definition of the property member of the feature type", title="Definition" + ) + cardinality: Optional[Cardinality] = Field( + None, description="Definition of the member type cardinality", title="Cardinality" + ) + code: Optional[str] = Field(None, description="Code for the attribute member of the feature type", title="Code") + valueMeasurementUnit: Optional[str] = Field( + None, description="Measurement unit of the values (in case of variable)", title="Value measurement unit" + ) + valueType: Optional[str] = Field( + None, + description=( + "Type of value. A good practice is to rely on primitive data types defined in the XML Schema" + " https://www.w3.org/2009/XMLSchema/XMLSchema.xsd" + ), + title="Value type", + ) + listedValue: Optional[List[ListedValueItem]] = Field( + None, description="List of controlled value(s) used in te attribute member", title="Listed value(s)" + ) + + +class FeatureTypeItem(SchemaBaseModel): + typeName: Optional[str] = Field( + None, + description=( + "text string that uniquely identifies this feature type within the feature catalogue that contains this" + " feature type" + ), + title="Type name", + ) + definition: Optional[str] = Field( + None, description="definition of the feature type in a natural language", title="Definition" + ) + code: Optional[str] = Field( + None, + description=( + "code that uniquely identifies this feature type within the feature catalogue that contains this feature" + " type" + ), + title="Code", + ) + isAbstract: Optional[bool] = Field( + None, description="indicates if the feature type is abstract or not", title="Is abstract" + ) + aliases: Optional[List[str]] = Field(None, description="equivalent name(s) of this feature type", title="Alias(es)") + carrierOfCharacteristics: Optional[List[CarrierOfCharacteristic]] = Field( + None, + description="links this feature type to the property types that it contains", + title="Carrier(s) of characteristics", + ) + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class ModelInfoItem(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + author: Optional[str] = Field(None, title="Author") + version: Optional[str] = Field(None, title="Version") + model_id: Optional[str] = Field(None, title="Model Identifier") + nb_topics: Optional[float] = Field(None, title="Number of topics") + description: Optional[str] = Field(None, title="Description") + corpus: Optional[str] = Field(None, title="Corpus name") + uri: Optional[str] = Field(None, title="URI") + + +class TopicWord(SchemaBaseModel): + word: Optional[str] = Field(None, title="Word") + word_weight: Optional[float] = Field(None, title="Word weight") + + +class TopicDescriptionItem(SchemaBaseModel): + topic_id: Optional[Union[int, str]] = Field(None, title="Topic identifier") + topic_score: Optional[Union[float, str]] = Field(None, title="Topic score") + topic_label: Optional[str] = Field(None, title="Topic label") + topic_words: Optional[List[TopicWord]] = Field(None, description="Words", title="Topic words") + + +class LdaTopic(SchemaBaseModel): + class Config: + extra = Extra.forbid + + model_info: Optional[List[ModelInfoItem]] = Field(None, title="Model information") + topic_description: Optional[List[TopicDescriptionItem]] = Field(None, title="Topic information") + + +class Embedding(SchemaBaseModel): + id: str = Field(..., title="Vector Model ID") + description: Optional[str] = Field(None, title="Vector Model Description") + date: Optional[str] = Field(None, title="Date (YYYY-MM-DD)") + vector: Dict[str, Any] = Field(..., title="Vector") + + +class ResourceSchema(SchemaBaseModel): + """ + External resource schema + """ + + dctype: Optional[str] = Field( + "doc/oth", + description=( + "Document types for external resource e.g. `doc/adm` \n* `doc/adm` - Document, Administrative [doc/adm] \n*" + " `doc/anl` - Document, Analytical [doc/anl] \n* `doc/oth` - Document, Other [doc/oth] \n* `doc/qst` -" + " Document, Questionnaire [doc/qst] \n* `doc/ref` - Document, Reference [doc/ref] \n* `doc/rep` - Document," + " Report [doc/rep] \n* `doc/tec` - Document, Technical [doc/tec] \n* `aud` - Audio [aud]\n* `dat` -" + " Database [dat]\n* `map` - Map [map]\n* `dat/micro` - Microdata File [dat/micro]\n* `pic` - Photo [pic]\n*" + " `prg` - Program [prg]\n* `tbl` - Table [tbl]\n* `vid` - Video [vid] \n* `web` - Web Site [web]" + ), + title="Resource type", + ) + dcformat: Optional[str] = Field( + None, + description=( + "Document file format e.g. `application/zip` \n* `application/x-compressed` - Compressed, Generic \n*" + " `application/zip` - Compressed, ZIP \n* `application/x-cspro` - Data, CSPro \n* `application/dbase` -" + " Data, dBase \n* `application/msaccess` - Data, Microsoft Access \n* `application/x-sas` - Data, SAS " + " \n* `application/x-spss` - Data, SPSS \n* `application/x-stata` - Data, Stata \n* `text` - Document," + " Generic \n* `text/html` - Document, HTML \n* `application/msexcel` - Document, Microsoft Excel \n*" + " `application/mspowerpoint` - Document, Microsoft PowerPoint \n* `application/msword` - Document," + " Microsoft Word \n* `application/pdf` - Document, PDF \n* `application/postscript` - Document," + " Postscript \n* `text/plain` - Document, Plain \n* `text/wordperfect` - Document, WordPerfect \n*" + " `image/gif` - Image, GIF \n* `image/jpeg` - Image, JPEG \n* `image/png` - Image, PNG \n*" + " `image/tiff` - Image, TIFF" + ), + title="Resource Format", + ) + title: str = Field(..., description="Title") + author: Optional[str] = Field(None, description="Author") + dcdate: Optional[str] = Field(None, description="Date") + country: Optional[str] = Field(None, description="Country") + language: Optional[str] = Field(None, description="Language") + contributor: Optional[str] = Field(None, description="Contributor") + publisher: Optional[str] = Field(None, description="Publisher") + rights: Optional[str] = Field(None, description="Rights") + description: Optional[str] = Field(None, description="Description") + abstract: Optional[str] = Field(None, description="Abstract") + toc: Optional[str] = Field(None, description="TOC") + filename: Optional[str] = Field( + None, + description=( + "Resource file name or URL. For uploading a file, use the field `file` in formData or use the `Upload file`" + " endpoint." + ), + ) + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class CharacterSet(SchemaBaseModel): + """ + Character encoding used e.g. UTF-8 + """ + + codeListValue: Optional[str] = Field( + None, + title=( + "Character set code, e.g 'utf8'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_CharacterSetCode) CharacterSet" + " codelist" + ), + ) + codeList: Optional[str] = Field( + None, + title=( + "Codelist used for character sets. Recommended URI:" + " http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_CharacterSetCode" + ), + ) + + +class Date(SchemaBaseModel): + """ + Date + """ + + date: str = Field(..., description="Date in ISO 8601 format - YYYY-MM-DD", title="Date") + type: Optional[str] = Field( + None, + description=( + "Date type e.g. `publication`, `revision`, `creation`, `expiry`, `lastUpdate`, `lastRevision`," + " `deprecated`. See full list at" + " [data.noaa.gov](https://data.noaa.gov/resources/iso19139/schema/resources/Codelist/gmxCodelists.xml#CI_DateTypeCode)" + ), + title="Date type", + ) + + +class OnlineResource(SchemaBaseModel): + """ + Online Resource + """ + + linkage: Optional[str] = Field(None, title="Link to the resource") + name: Optional[str] = Field(None, title="Resource title") + description: Optional[str] = Field(None, title="Resource description") + protocol: Optional[str] = Field( + None, description="Protocol used to access the resource, eg HTTP, FTP", title="Protocol" + ) + function: Optional[str] = Field(None, description="Function of the online resource", title="Function") + + +class Phone(SchemaBaseModel): + """ + Phone contact information + """ + + voice: Optional[str] = Field(None, title="Phone number") + facsimile: Optional[str] = Field(None, title="Facsimile") + + +class Address(SchemaBaseModel): + """ + Address contact information + """ + + deliveryPoint: Optional[str] = Field(None, title="Delivery point") + city: Optional[str] = Field(None, title="City") + postalCode: Optional[str] = Field(None, title="Postal Code") + country: Optional[str] = Field(None, title="Country") + elctronicMailAddress: Optional[str] = Field(None, title="Email") + + +class ContactInfo(SchemaBaseModel): + """ + Information to contact the responsible party + """ + + phone: Optional[Phone] = Field(None, description="Phone contact information", title="Phone") + address: Optional[Address] = Field(None, description="Address contact information", title="Address") + onlineResource: Optional[OnlineResource] = None + + +class ResponsibleParty(SchemaBaseModel): + """ + Definition of a responsible party (individual or organization) + """ + + individualName: Optional[str] = Field(None, description="Name of the individual", title="Individual name") + organisationName: Optional[str] = Field(None, description="Name of the organization", title="Organization name") + positionName: Optional[str] = Field(None, description="Name of the individual position", title="Position name") + contactInfo: Optional[ContactInfo] = Field( + None, description="Information to contact the responsible party", title="Contact info" + ) + role: Optional[str] = Field( + None, + description=( + "Role of the responsible party. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#CI_RoleCode) Role codelist." + " Suggested values: {`resourceProvider`, `custodian`, `owner`, `sponsor`, `user`, `distributor`," + " `originator`, `pointOfContact`, `principalInvestigator`, `processor`, `publisher`, `author`, `coAuthor`," + " `collaborator`, `editor`, `mediator`, `rightsHolder`, `contributor`, `funder`, `stakeholder`}" + ), + title="Role", + ) + + +class IdentifierItem(SchemaBaseModel): + authority: Optional[str] = Field( + None, description="The authority that identifies uniquely the resource metadata", title="Authority" + ) + code: Optional[Any] = Field(None, description="A code uniquely identifying the resource metadata", title="Code") + + +class Series(SchemaBaseModel): + """ + Series citation + """ + + name: Optional[str] = Field(None, description="Name of the series in which the resource is cited", title="Name") + issueIdentification: Optional[str] = Field(None, description="Identification of the series issue", title="Issue") + page: Optional[str] = Field( + None, description="Identification of the series page in which the resource is cited", title="Page" + ) + + +class Citation(SchemaBaseModel): + """ + A set of elements to describe a resource citation + """ + + title: Optional[str] = Field(None, description="Resource title", title="Title") + alternateTitle: Optional[List[str]] = Field(None, description="Resource alternate title", title="Alternate Title") + date: Optional[List[Date]] = Field( + None, description="Date(s) associated to the resource citation", title="Citation date(s)" + ) + edition: Optional[str] = Field(None, description="Edition", title="Edition") + editionDate: Optional[str] = Field( + None, + description=( + "Date and time when the metadata record was created or updated. Requires an extended ISO 8601 formatted" + " combined UTC date and time string (2009-11-17T10:00:00)" + ), + title="Edition Date", + ) + identifier: Optional[List[IdentifierItem]] = Field( + None, description="Identifiers for the resource metadata", title="Identifier" + ) + citedResponsibleParty: Optional[List[ResponsibleParty]] = Field( + None, description="Responsible party(ies) to cite in the resource citation", title="Responsible party(ies)" + ) + presentationForm: Optional[List[Any]] = Field( + None, + description=( + "The resource presentation form. e.g. 'mapDigital'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#CI_PresentationFormCode)" + " PresentationForm codelist. Suggested values: {`documentDigital`, `imageDigital`, `documentHardcopy`," + " `imageHardcopy`, `mapDigital`, `mapHardcopy`, `modelDigital`, `modelHardcopy`, `profileDigital`," + " `profileHardcopy`, `tableDigital`, `tableHardcopy`, `videoDigital`, `videoHardcopy`, `audioDigital`," + " `audioHardcopy`, `multimediaDigital`, `multimediaHardcopy`, `physicalSample`, `diagramDigital`," + " `diagramHardcopy`}" + ), + title="Presentation form", + ) + series: Optional[Series] = Field(None, description="Series citation", title="Series") + otherCitationDetails: Optional[str] = Field(None, title="Other Citation Details") + collectiveTitle: Optional[str] = Field(None, title="Collective Title") + ISBN: Optional[str] = Field(None, title="ISBN") + ISSN: Optional[str] = Field(None, title="ISSN") + + +class ReferenceSystem(SchemaBaseModel): + """ + Reference System + """ + + code: Optional[str] = Field(None, description="example - 5701", title="Reference System Identifier Code") + codeSpace: Optional[str] = Field(None, description="example - 'EPSG'", title="Code Space") + + +class UpdateScopeItem(SchemaBaseModel): + scope: Optional[str] = Field( + None, description="Scope of data to which maintenance is applied", title="Update Scope" + ) + description: Optional[str] = Field( + None, + description="Additional information about the range or extent of the resource", + title="Update Scope Description", + ) + + +class MaintenanceInfo(SchemaBaseModel): + """ + Information about a resource maintenance + """ + + maintenanceAndUpdateFrequency: Optional[str] = Field( + None, + description=( + "Frequency of maintenance/update of a resource. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_MaintenanceFrequencyCode)" + " MaintenanceFrequency codelist. Suggested values: {`continual`, `daily`, `weekly`, `fortnightly`," + " `monthly`, `quarterly`, `biannually`, `annually`, `asNeeded`, `irregular`, `notPlanned`, `unknown`}" + ), + title="Maintenance and Update Frequency", + ) + dateOfNextUpdate: Optional[str] = Field( + None, + description=( + "Date of the next update of the resource. Requires an extended ISO 8601 formatted combined UTC date and" + " time string (2009-11-17T10:00:00)" + ), + title="Date of Next Update", + ) + userDefinedMaintenanceFrequency: Optional[str] = Field( + None, description="User defined maintenance frequency", title="User Defined Maintenance Frequency" + ) + updateScope: Optional[List[UpdateScopeItem]] = Field( + None, description="Scope of data to which maintenance is applied", title="Update Scope" + ) + maintenanceNote: Optional[List[str]] = Field( + None, description="Note about the maintenance", title="Maintenance Note" + ) + contact: Optional[ResponsibleParty] = Field( + None, description="Contact information for the maintenance", title="Contact" + ) + + +class GraphicOverview(SchemaBaseModel): + """ + Graphic overview of resource + """ + + fileName: Optional[str] = Field(None, title="File name / URL") + fileDescription: Optional[str] = Field(None, title="File Description") + fileType: Optional[str] = Field(None, title="File Type") + + +class Format(SchemaBaseModel): + """ + Description of a digital format + """ + + name: Optional[str] = Field(None, title="Format name") + version: Optional[str] = Field(None, title="Format version") + amendmentNumber: Optional[str] = Field(None, title="Format version amendment number") + specification: Optional[str] = Field(None, title="Format specification") + fileDecompressionTechnique: Optional[str] = Field(None, title="File decompression technique") + FormatDistributor: Optional[ResponsibleParty] = Field( + None, description="Responsible party in charge of the format distribution", title="Distributor" + ) + + +class Keywords(SchemaBaseModel): + """ + Set of keywords for a given type of keywords, eg. theme, project, instrument + """ + + type: Optional[str] = Field( + None, + description=( + "Type of keyword based on pre-defined code values. based on (but not limited to) code values listed in the" + " ISO 19115 \n {`dataCenter`, `discipline`, `place`, `dataResolution`," + " \n`stratum`,`temporal`,`theme`,`dataCentre`,`featureType`,`instrument`,`platform`,`process`,`project`,`service`,`product`,`subTopicCategory`}" + ), + title="Keyword type", + ) + keyword: str = Field(..., description="Keywords") + thesaurusName: Optional[str] = Field( + None, description="Thesaurus to which keywords are associated", title="Thesaurus" + ) + + +class LegalConstraints(SchemaBaseModel): + """ + Legal constraints associated to the resource + """ + + useLimitation: Optional[List[str]] = None + accessConstraints: Optional[List[str]] = Field( + None, + description=( + "A restriction to access/use a resource. e.g. 'dataset'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_RestrictionCode) Restriction" + " codelist. Suggested values: {`copyright`, `patent`, `patentPending`, `trademark`, `license`," + " `intellectualPropertyRights`, `restricted`, `otherRestrictions`, `unrestricted`, `licenceUnrestricted`," + " `licenceEndUser`, `licenceDistributor`, `private`, `statutory`, `confidential`, `SBU`, `in-confidence`}" + ), + title="Access constraints", + ) + useConstraints: Optional[List[str]] = Field( + None, + description=( + "Legal constraints concerning the use of the resource, e.g. Terms of use statement, License. Recommended" + " code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_RestrictionCode) Restriction" + " codelist. Suggested values: {`copyright`, `patent`, `patentPending`, `trademark`, `license`," + " `intellectualPropertyRights`, `restricted`, `otherRestrictions`, `unrestricted`, `licenceUnrestricted`," + " `licenceEndUser`, `licenceDistributor`, `private`, `statutory`, `confidential`, `SBU`, `in-confidence`}" + ), + title="Use constraints", + ) + otherConstraints: Optional[List[str]] = Field( + None, + description=( + "Other legal constraints concerning the resource, e.g. additional information to complement the access/use" + " constraints, Disclaimer" + ), + title="Other constraints", + ) + + +class SecurityConstraints(SchemaBaseModel): + """ + Security constraints associated to the resource + """ + + useLimitation: Optional[List[str]] = None + classification: Optional[str] = Field( + None, + description=( + "Security constraint classification , e.g. 'secret'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_ClassificationCode)" + " Classification codelist. Suggested values: {`unclassified`, `restricted`, `confidential`, `secret`," + " `topSecret`, `SBU`, `forOfficialUseOnly`, `protected`, `limitedDistribution`}" + ), + title="Classification", + ) + userNote: Optional[str] = Field(None, title="User note") + classificationSystem: Optional[str] = Field( + None, + description=( + "A specific classification system, eg. Organization-specific system to classify resource confidentiality" + ), + title="Classification system", + ) + handlingDescription: Optional[str] = Field( + None, + description="A description for the security constraint associated to the resource", + title="Handling description", + ) + + +class Constraints(SchemaBaseModel): + """ + Constraints associated to the resource + """ + + legalConstraints: Optional[LegalConstraints] = Field( + None, description="Legal constraints associated to the resource", title="Legal constraints" + ) + securityConstraints: Optional[SecurityConstraints] = Field( + None, description="Security constraints associated to the resource", title="Security constraints" + ) + + +class Parameter(SchemaBaseModel): + """ + Service parameter + """ + + name: Optional[str] = Field(None, description="Service parameter name", title="Name") + direction: Optional[str] = Field( + None, description="Direction of the parameter. Suggested values: {in, out, inout}", title="Direction" + ) + description: Optional[str] = Field(None, description="Service parameter description", title="Description") + optionality: Optional[str] = Field( + None, description="Optionality, either 'Optional' or 'Mandatory' value", title="Optionality" + ) + repeatability: Optional[bool] = Field(None, description="Service parameter repeatability", title="Repeatability") + valueType: Optional[str] = Field(None, description="Value type", title="Value type") + + +class DCPItem(SchemaBaseModel): + """ + Distributed Computing Plateform + """ + + codeListValue: Optional[str] = Field( + None, + title=( + "DCP, eg. 'WebServices'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DCPList) DCP codelist. Suggested" + " values: {COM, CORBA, JAVA, SQL, WebServices, XML}" + ), + ) + codeList: Optional[str] = Field( + None, + title=( + "Codelist used for DCPs. Recommended URI:" + " http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DCPList" + ), + ) + codeSpace: Optional[str] = Field(None, title="Codespace of the DCPs. Recommended value: ISOTC211/19119") + + +class OperationMetadata(SchemaBaseModel): + """ + Service operation metadata descriptions + """ + + operationName: Optional[str] = Field(None, title="Operation name") + DCP: Optional[List[DCPItem]] = Field( + None, description="Distributed Computing Plateform(s). Recommended value: 'WebServices'", title="DCP(s)" + ) + operationDescription: Optional[str] = Field(None, title="Operation description") + invocationName: Optional[str] = Field(None, title="Invocation name") + parameters: Optional[List[Parameter]] = Field(None, description="Operation parameters", title="Parameters") + connectPoint: Optional[OnlineResource] = Field(None, title="Connect Point(s)") + dependsOn: Optional[List[OperationMetadata]] = Field( + None, description="Depends on (other operation metadata)", title="Depends on" + ) + + +class ResourceSpecificUsageItem(SchemaBaseModel): + specificUsage: Optional[str] = Field( + None, + description=( + "A description of a specific usage of this resource relevant to highlight, eg. use case of interest," + " success story, data paper" + ), + title="Specific usage description", + ) + usageDateTime: Optional[str] = Field( + None, + description=( + "Date and time of the usage. Requires an extended ISO 8601 formatted combined UTC date and time string" + " (2009-11-17T10:00:00)" + ), + title="Metadata Date Stamp", + ) + userDeterminedLimitations: Optional[str] = Field(None, title="User determined limitations") + userContactInfo: Optional[List[ResponsibleParty]] = Field(None, title="User contact(s)") + + +class ServiceIdentification(SchemaBaseModel): + """ + Service identification + """ + + serviceType: Optional[str] = Field(None, description="Service type name", title="Service type") + serviceTypeVersion: Optional[str] = Field(None, description="Service type version", title="Service type version") + accessProperties: Optional[AccessProperties] = Field(None, title="Access properties") + restrictions: Optional[List[Constraints]] = Field( + None, description="Constraints associated to the service", title="Service constraints" + ) + keywords: Optional[List[Keywords]] = Field( + None, description="Service keywords, organized by keyword type", title="Service keywords" + ) + coupledResource: Optional[List[CoupledResourceItem]] = Field( + None, description="Coupled resource(s)", title="Coupled resource(s)" + ) + couplingType: Optional[CouplingType] = Field(None, description="Coupling type", title="Coupling type") + containsOperations: Optional[List[OperationMetadata]] = Field( + None, description="Operation(s) contained in the service", title="Contained operation(s)" + ) + operatesOn: Optional[List[OperatesOnItem]] = Field( + None, + description="List identifiers of datasets on which service operates on", + title="Operates on relationship(s)", + ) + + +class IdentificationInfo(SchemaBaseModel): + """ + Identification(s) of the resource + """ + + citation: Optional[Citation] = Field(None, description="Dataset citation", title="Citation") + abstract: Optional[str] = Field(None, description="Abstract describing the dataset resource", title="Abstract") + purpose: Optional[str] = Field(None, description="Purpose of the dataset resource", title="Purpose") + credit: Optional[str] = Field(None, description="Credit associated to the dataset resource", title="Credit") + status: Optional[List[str]] = Field( + None, + description=( + "Status of the dataset resource. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_ProgressCode) Progress codelist." + " Suggested values: {`completed`, `historicalArchive`, `obsolete`, `onGoing`, `planned`, `required`," + " `underDevelopment`, `final`, `pending`, `retired`, `superseded`, `tentative`, `valid`, `accepted`," + " `notAccepted`, `withdrawn`, `proposed`, `deprecated`}" + ), + title="Status", + ) + pointOfContact: Optional[List[ResponsibleParty]] = Field( + None, description="One or more points of contacts for the resource", title="Points of contact" + ) + resourceMaintenance: Optional[List[MaintenanceInfo]] = Field( + None, description="Information about the dataset resource maintenance", title="Resource maintenance" + ) + graphicOverview: Optional[List[GraphicOverview]] = Field( + None, description="Graphic Overview(s) for the dataset resource", title="Graphic Overview(s)" + ) + resourceFormat: Optional[List[Format]] = Field(None, description="Resource format(s)", title="Resource format(s)") + descriptiveKeywords: Optional[List[Keywords]] = Field( + None, description="Descriptive keywords, organized by keyword type", title="Descriptive keywords" + ) + resourceConstraints: Optional[List[Constraints]] = Field( + None, description="Constraints associated to the resource", title="Resource constraints" + ) + resourceSpecificUsage: Optional[List[ResourceSpecificUsageItem]] = Field( + None, description="Resource specific usage(s) - if applicable", title="Resource specific usage(s)" + ) + aggregationInfo: Optional[AggregationInfo] = Field( + None, + description="Identification of aggregate that encompasses the described resource, eg. data collection", + title="Aggregate information", + ) + extent: Optional[Extent] = Field( + None, + description=( + "Defines the spatial (horizontal and vertical) and temporal region to which the content of the resource" + " applies." + ), + title="Extent of the resource", + ) + spatialRepresentationType: Optional[List[str]] = Field( + None, + description=( + "Spatial representation type of the resource. e.g. 'vector'. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_SpatialRepresentationTypeCode)" + " SpatialRepresentationType codelist. Suggested values: {`vector`, `grid`, `textTable`, `tin`," + " `stereoModel`, `video`}" + ), + title="Spatial Representation type", + ) + spatialResolution: Optional[List[SpatialResolutionItem]] = Field( + None, description="Spatial resolution of the resource", title="Spatial Resolution" + ) + language: Optional[List[str]] = Field( + None, + description=( + "Resource language(s). Preferred code following the [ISO 639-2](http://www.loc.gov/standards/iso639-2/)" + " (alpha-3 code)" + ), + title="Resource language(s)", + ) + characterSet: Optional[List[CharacterSet]] = Field( + None, description="Resource character set(s)", title="Resource character set(s)" + ) + topicCategory: Optional[List[str]] = Field( + None, + description=( + "Topic category of the resource. e.g. `owner`. Recommended code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_TopicCategoryCode) TopicCategory" + " codelist. Suggested values: {`farming`, `biota`, `boundaries`, `climatologyMeteorologyAtmosphere`," + " `economy`, `elevation`, `environment`, `geoscientificInformation`, `health`, `imageryBaseMapsEarthCover`," + " `intelligenceMilitary`, `inlandWaters`, `location`, `oceans`, `planningCadastre`, `society`, `structure`," + " `transportation`, `utilitiesCommunication`, `extraTerrestrial`, `disaster`}" + ), + title="Topic categories", + ) + supplementalInformation: Optional[str] = Field( + None, description="Additional information about the resource", title="Supplemental Information" + ) + serviceIdentification: Optional[ServiceIdentification] = Field( + None, description="Service identification", title="Service identification" + ) + + +class FeatureCatalogueDescription(SchemaBaseModel): + """ + description of the feature catalogue (ISO 19110) associated to a vector resource, ie. the definition of the vector data structure + """ + + complianceCode: Optional[bool] = Field( + None, + description="Indicates whether the dataset complies with the feature catalogue description", + title="Compliance code", + ) + language: Optional[str] = Field(None, title="Feature catalogue language") + includedWithDataset: Optional[bool] = Field( + None, + description="Indicates if the feature catalogue (ISO 19110) is included with the dataset?", + title="Included with dataset", + ) + featureCatalogueCitation: Optional[Citation] = Field(None, title="Feature Catalogue citation") + + +class ContentInfoItem(SchemaBaseModel): + featureCatalogueDescription: Optional[FeatureCatalogueDescription] = Field( + None, + description=( + "description of the feature catalogue (ISO 19110) associated to a vector resource, ie. the definition of" + " the vector data structure" + ), + title="Feature Catalogue Description", + ) + coverageDescription: Optional[CoverageDescription] = Field( + None, + description="description of the coverage (grid/raster), ie. the definition of the grid/raster data structure", + title="Coverage Description", + ) + + +class TransferOptions(SchemaBaseModel): + """ + Options of digital transfer available for the resource + """ + + onLine: Optional[List[ResourceSchema]] = Field(None, title="Online Resources") + + +class DistributionInfo(SchemaBaseModel): + """ + Distribution information + """ + + distributionFormat: Optional[List[Format]] = Field(None, title="Distribution format(s)") + distributor: Optional[List[ResponsibleParty]] = Field( + None, description="Responsible party(ies) in charge of the resource distribution", title="Distributor(s)" + ) + transferOptions: Optional[TransferOptions] = Field( + None, description="Options of digital transfer available for the resource", title="Digital transfer options" + ) + + +class ResultItem(SchemaBaseModel): + specification: Optional[Citation] = Field( + None, description="The specification(s) of the data quality conformance result", title="Result specification" + ) + explanation: Optional[str] = Field( + None, + description="The explanation(s) of the data quality conformance (or non-conformance) result", + title="Result explanation", + ) + pass_: Optional[bool] = Field( + None, + alias="pass", + description="Indicates whether or not the conformance result is a success", + title="Result pass", + ) + + +class Result(SchemaBaseModel): + """ + Result of conformance of the resource + """ + + nameOfMeasure: Optional[List[str]] = Field(None, description="Data quality measure names", title="Measures") + measureIdentification: Optional[str] = Field( + None, description="Unique identifier for the data quality measure", title="Measure identification" + ) + measureDescription: Optional[str] = Field( + None, description="Description for the data quality measure", title="Measure description" + ) + evaluationMethodType: Optional[List[str]] = Field( + None, + description=( + "The type of method to evaluate the data quality measure. e.g. 'indirect'. Recommended code following the" + " [ISO/TS 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#DQ_EvaluationMethodTypeCode)" + " TopicCategory codelist. Suggested values: {`directInternal`, `directExternal`, `indirect`}" + ), + title="Evaluation method type", + ) + evaluationMethodDescription: Optional[str] = Field( + None, description="a description of the data quality evaluation method", title="Evaluation method description" + ) + evaluationProcedure: Optional[Citation] = Field( + None, description="Evaluation procedure description (as 'citation')", title="Evaluation procedure" + ) + dateTime: Optional[str] = Field( + None, + description=( + "Date and time when the data quality report has been established. Requires an extended ISO 8601 formatted" + " combined UTC date and time string (2009-11-17T10:00:00)" + ), + title="Report Date Stamp", + ) + result: Optional[List[ResultItem]] = Field( + None, description="Result(s) of consistency associated to the data quality report", title="Result(s)" + ) + + +class DQDomainConsistency(SchemaBaseModel): + """ + Domain consistency report information + """ + + result: Optional[Result] = Field( + None, description="Result of conformance of the resource", title="Conformance Result" + ) + + +class ReportItem(SchemaBaseModel): + DQ_DomainConsistency: Optional[DQDomainConsistency] = Field( + None, description="Domain consistency report information", title="Data quality domain consistency" + ) + + +class SourceItem(SchemaBaseModel): + description: Optional[str] = Field(None, title="Source description") + sourceCitation: Optional[Citation] = Field(None, title="Source citation") + + +class ProcessStepItem(SchemaBaseModel): + description: Optional[str] = Field( + None, description="description of the process step", title="process step description" + ) + rationale: Optional[str] = Field(None, description="rationale of the process step", title="process step rationale") + dateTime: Optional[str] = Field( + None, + description=( + "Date and time when the data quality report has been established. Requires an extended ISO 8601 formatted" + " combined UTC date and time string (2009-11-17T10:00:00)" + ), + title="Date stamp", + ) + processor: Optional[List[ResponsibleParty]] = Field( + None, description="Responsible party(ies) in charge of the processing for the step", title="Processor(s)" + ) + source: Optional[List[SourceItem]] = Field( + None, description="Source(s) processed during the process step", title="Source(s)" + ) + + +class Lineage(SchemaBaseModel): + """ + Description of the resource lineage, ie process steps performed to lead to the resource + """ + + statement: Optional[str] = Field( + None, + description="A description of the overall lineage information. eg. 'Data management workflow'", + title="Lineage statement", + ) + processStep: Optional[List[ProcessStepItem]] = Field( + None, description="Description of the process steps required to obtain the resource", title="Process step(s)" + ) + + +class DataQualityInfoItem(SchemaBaseModel): + scope: Optional[str] = Field( + None, + description=( + "Scope(s), or 'hierarchy level(s)' applicable to the dataset description e.g. dataset, series. Recommended" + " code following the [ISO/TS" + " 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MX_ScopeCode) Scope codelist." + " Suggested values: `attribute`, `attributeType`, `collectionHardware`, `collectionSession`, `dataset`," + " `series`, `nonGeographicDataset`, `dimensionGroup`, `feature`, `featureType`, `propertyType`," + " `fieldSession`, `software`, `service`, `model`, `tile`, `initiative`, `stereomate`, `sensor`," + " `platformSeries`, `sensorSeries`, `productionSeries`, `transferAggregate`, `otherAggregate`" + ), + title="Scope of the data quality information", + ) + report: Optional[List[ReportItem]] = Field( + None, description="Data quality report(s) associated to the resource", title="Data quality report(s)" + ) + lineage: Optional[Lineage] = Field( + None, + description="Description of the resource lineage, ie process steps performed to lead to the resource", + title="Lineage", + ) + + +class PortrayalCatalogueInfo(SchemaBaseModel): + """ + Information identifying the portrayal catalogue used by the resource + """ + + portrayalCatalogueCitation: Optional[List[Citation]] = Field(None, title="Citation for the portrayal catalogue") + + +class FeatureCatalogue(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name of the feature catalogue", title="Name") + scope: Optional[List[str]] = Field( + None, description="Subject domain(s) of feature types defined in this feature catalogue", title="Scope(s)" + ) + fieldOfApplication: Optional[List[str]] = Field( + None, + description="Description of kind(s) of use to which this feature catalogue may be put", + title="Field(s) of application", + ) + versionNumber: Optional[str] = Field( + None, + description=( + "version number of this feature catalogue, which may include both a major version number or letter and a" + " sequence of minor release numbers or letters, such as '3.2.4a.' The format of this attribute may differ" + " between cataloguing authorities." + ), + title="Version number", + ) + versionDate: Optional[Date] = None + producer: Optional[ResponsibleParty] = Field( + None, + description=( + "Name, address, country, and telecommunications address of person or organization having primary" + " responsibility for the intellectual content of this feature catalogue" + ), + title="Producer", + ) + functionalLanguage: Optional[str] = Field( + None, + description=( + "Formal functional language in which the feature operation formal definition occurs in this feature" + " catalogue" + ), + title="Functional language", + ) + featureType: Optional[List[FeatureTypeItem]] = Field( + None, description="Feature type(s) contained in the catalogue", title="Feature type(s)" + ) + + +class Description(SchemaBaseModel): + idno: str = Field(..., description="Global unique persistent identifier", title="Unique Identifier") + language: Optional[str] = Field(None, description="Main metadata language", title="Language") + characterSet: Optional[CharacterSet] = Field( + None, description="Metadata Character encoding used e.g. UTF-8", title="Character set" + ) + parentIdentifier: Optional[str] = Field( + None, + description=( + "Global unique persistent identifier of the parent record, eg. a data collection that includes several" + " datasets" + ), + title="Unique parent identifier", + ) + hierarchyLevel: Optional[str] = Field( + None, + description=( + "List of Scope(s), or 'hierarchy level(s)'. e.g. `dataset`, `service`. Recommended code following the" + " [ISO/TS 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MX_ScopeCode) Scope" + " codelist. (string)" + ), + title="Scope(s) / Hierarchy Level(s)", + ) + hierarchyLevelName: Optional[str] = Field( + None, + description="List of scope / hierarchy level names. Alternative to the use of 'hierarchyLevel' code items.", + title="Scope / hierarchy level names.", + ) + contact: Optional[List[ResponsibleParty]] = Field( + None, description="Contact(s) associated to the metadata", title="Contacts" + ) + dateStamp: Optional[str] = Field( + None, + description=( + "Date and time when the metadata record was created or updated. Requires an extended ISO 8601 formatted" + " combined UTC date and time string (2009-11-17T10:00:00)" + ), + title="Metadata Date Stamp", + ) + metadataStandardName: Optional[str] = Field( + None, + description="Standard name of the metadata standard used. Recommended value: ISO 19115:2003/19139", + title="Metadata standard name", + ) + metadataStandardVersion: Optional[str] = Field( + None, + description=( + "Version of the metadata standard used. Optional for ISO/TC211 standard if the metadata standard name" + " includes the inception year" + ), + title="Metadata standard version", + ) + dataSetURI: Optional[str] = Field( + None, description="A URI that uniquely identifies the dataset", title="Dataset URI" + ) + spatialRepresentationInfo: Optional[List[SpatialRepresentationInfoItem]] = Field( + None, title="Resource Spatial Representation(s)" + ) + referenceSystemInfo: Optional[List[ReferenceSystem]] = Field( + None, + description=( + "Resource's spatial reference systems - Description of the spatial and/or temporal reference systems used" + " in the dataset." + ), + title="Resource Reference Systems", + ) + identificationInfo: Optional[IdentificationInfo] = Field( + None, description="Identification(s) of the resource", title="Identification Info(s)" + ) + contentInfo: Optional[List[ContentInfoItem]] = Field( + None, + description="Information on the resource content, ie data structure definition", + title="Content information", + ) + distributionInfo: Optional[DistributionInfo] = Field( + None, description="Distribution information", title="Distribution information" + ) + dataQualityInfo: Optional[List[DataQualityInfoItem]] = Field( + None, description="Data quality information", title="Data quality information" + ) + metadataMaintenance: Optional[MaintenanceInfo] = Field( + None, description="Metadata maintenance information", title="Metadata Maintenance information" + ) + portrayalCatalogueInfo: Optional[PortrayalCatalogueInfo] = Field( + None, + description="Information identifying the portrayal catalogue used by the resource", + title="Portrayal catalogue information", + ) + metadataExtensionInfo: Optional[Any] = None + applicationSchemaInformation: Optional[Any] = None + thesaurusInfo: Optional[List[Citation]] = Field( + None, description="Thesaurus referenced by keywords", title="Thesaurus" + ) + feature_catalogue: Optional[FeatureCatalogue] = Field(None, title="Feature catalogue") + + +class GeospatialSchema(SchemaBaseModel): + """ + Geospatial draft schema + """ + + idno: Optional[str] = Field(None, description="Project unique identifier", title="Project unique identifier") + metadata_information: Optional[MetadataInformation] = Field( + None, description="Document description", title="Document metadata information" + ) + description: Description = Field(..., title="Geospatial schema") + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags") + lda_topics: Optional[List[LdaTopic]] = Field(None, description="LDA topics", title="LDA topics") + embeddings: Optional[List[Embedding]] = Field(None, description="Word embeddings", title="Word embeddings") + additional: Optional[Dict[str, Any]] = Field( + None, description="Any additional metadata", title="Additional metadata" + ) + + +class CharacterEncoding(SchemaBaseModel): + """ + Character encoding used e.g. UTF-8 + """ + + characterSet: Optional[CharacterSet] = Field( + None, description="Character encoding used e.g. UTF-8", title="Character set" + ) + + +class Locale(SchemaBaseModel): + """ + Locale definition for multi-lingual description + """ + + id: Optional[str] = Field(None, description="Locale code, eg. FR, EN", title="Locale code") + languageCode: Optional[str] = Field(None, description="Language", title="Language") + characterEncoding: Optional[CharacterEncoding] = Field( + None, description="Character encoding used e.g. UTF-8", title="Character set" + ) + + +OperationMetadata.update_forward_refs() diff --git a/pydantic_schemas/image_schema.py b/pydantic_schemas/image_schema.py new file mode 100644 index 0000000..24c7168 --- /dev/null +++ b/pydantic_schemas/image_schema.py @@ -0,0 +1,1182 @@ +# generated by datamodel-codegen: +# filename: image-schema.json +# timestamp: 2024-09-13T19:00:23+00:00 + +from __future__ import annotations + +from datetime import datetime +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import AnyUrl, Extra, Field, confloat, constr + +from .utils.schema_base_model import SchemaBaseModel + + +class Overwrite(Enum): + """ + Overwrite document if already exists? + """ + + yes = "yes" + no = "no" + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class MetadataInformation(SchemaBaseModel): + """ + Document description + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + production_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version: Optional[str] = Field( + None, description="Identify and describe the current version of the document", title="Document version" + ) + + +class Identifier(SchemaBaseModel): + type: Optional[str] = Field( + None, description="Type of identifier e.g. `doi`, `handle`, `other`", title="Identifier type" + ) + identifier: str = Field(..., title="Identifier") + + +class LicenseItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="License") + uri: Optional[str] = Field(None, title="URI") + + +class AlbumItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name of album") + description: Optional[str] = Field(None, title="Description") + owner: Optional[str] = Field(None, title="Owner") + uri: Optional[str] = Field(None, title="URI") + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class ModelInfoItem(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + author: Optional[str] = Field(None, title="Author") + version: Optional[str] = Field(None, title="Version") + model_id: Optional[str] = Field(None, title="Model Identifier") + nb_topics: Optional[float] = Field(None, title="Number of topics") + description: Optional[str] = Field(None, title="Description") + corpus: Optional[str] = Field(None, title="Corpus name") + uri: Optional[str] = Field(None, title="URI") + + +class TopicWord(SchemaBaseModel): + word: Optional[str] = Field(None, title="Word") + word_weight: Optional[float] = Field(None, title="Word weight") + + +class TopicDescriptionItem(SchemaBaseModel): + topic_id: Optional[Union[int, str]] = Field(None, title="Topic identifier") + topic_score: Optional[Union[float, str]] = Field(None, title="Topic score") + topic_label: Optional[str] = Field(None, title="Topic label") + topic_words: Optional[List[TopicWord]] = Field(None, description="Words", title="Topic words") + + +class LdaTopic(SchemaBaseModel): + class Config: + extra = Extra.forbid + + model_info: Optional[List[ModelInfoItem]] = Field(None, title="Model information") + topic_description: Optional[List[TopicDescriptionItem]] = Field(None, title="Topic information") + + +class Embedding(SchemaBaseModel): + id: str = Field(..., title="Vector Model ID") + description: Optional[str] = Field(None, title="Vector Model Description") + date: Optional[str] = Field(None, title="Date (YYYY-MM-DD)") + vector: Dict[str, Any] = Field(..., title="Vector") + + +class SceneCodesLabelledItem(SchemaBaseModel): + code: Optional[str] = Field(None, description="Scene code as a string of 6 digits", title="Scene Code") + label: Optional[str] = Field(None, description="Label", title="Scene Label") + description: Optional[str] = Field(None, description="Description of the scene", title="Scene Description") + + +class SubjectCodesLabelledItem(SchemaBaseModel): + code: Optional[str] = Field(None, description="Subject code as a string of 8 digits", title="Subject Code") + label: Optional[str] = Field(None, description="Label", title="Subject Label") + description: Optional[str] = Field(None, description="Description of the scene", title="Subject Description") + + +class Delimitertype(Enum): + spatial = "spatial" + temporal = "temporal" + + +class MediaFragment(SchemaBaseModel): + """ + Object defining this fragement of a media asset - if ommitted = the whole asset + """ + + class Config: + extra = Extra.forbid + + uri: AnyUrl + delimitertype: Optional[Delimitertype] = None + description: Optional[str] = None + + +class AltLangObject(SchemaBaseModel): + class Config: + extra = Extra.forbid + + __root__: Dict[ + constr( + regex=r"^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+))$" + ), + str, + ] = Field(..., description="Text in alternative languages") + + +class ArtworkOrObject(SchemaBaseModel): + class Config: + extra = Extra.forbid + + title: Optional[str] = Field( + None, + description="A reference for the artwork or object in the image.", + title="Title {Artwork or Object detail}", + ) + contentDescription: Optional[str] = Field( + None, + description="A textual description of the content depicted in the artwork or object.", + title="Content Description {Artwork or Object detail}", + ) + physicalDescription: Optional[str] = Field( + None, + description=( + "A textual description of the physical characteristics of the artwork or object, without reference to the" + " content depicted." + ), + title="Physical Description {Artwork or Object detail}", + ) + creatorNames: Optional[List[str]] = Field( + None, + description=( + "Contains the name of the artist who has created artwork or an object in the image. In cases where the" + " artist could or should not be identified the name of a company or organisation may be appropriate." + ), + title="Creator {Artwork or Object detail}", + ) + creatorIdentifiers: Optional[List[str]] = Field( + None, + description="Globally unique identifier for the creator of artwork or object.", + title="Creator ID {Artwork or Object detail}", + ) + contributionDescription: Optional[str] = Field( + None, + description="A textual description about a contribution made to an artwork or an object.", + title="Contribution Description {Artwork or Object detail}", + ) + stylePeriod: Optional[List[str]] = Field( + None, + description=( + "The style, historical or artistic period, movement, group, or school whose characteristics are represented" + " in the artwork or object." + ), + title="Style Period {Artwork or Object detail}", + ) + dateCreated: Optional[datetime] = Field( + None, + description=( + "Designates the date and optionally the time the artwork or object in the image was created. This relates" + " to artwork or objects with associated intellectual property rights." + ), + title="Date Created {Artwork or Object detail}", + ) + circaDateCreated: Optional[str] = Field( + None, + description=( + "Approximate date or range of dates associated with the creation and production of an artwork or object or" + " its components." + ), + title="Circa Date Created {Artwork or Object detail}", + ) + source: Optional[str] = Field( + None, + description=( + "The organisation or body holding and registering the artwork or object in the image for inventory" + " purposes." + ), + title="Source {Artwork or Object detail}", + ) + sourceInventoryNr: Optional[str] = Field( + None, + description=( + "The inventory number issued by the organisation or body holding and registering the artwork or object in" + " the image." + ), + title="Source Inventory Number {Artwork or Object detail}", + ) + sourceInventoryUrl: Optional[AnyUrl] = Field( + None, + description="URL reference to the metadata record of the inventory maintained by the Source.", + title="Source Inventory URL {Artwork or Object detail}", + ) + currentCopyrightOwnerName: Optional[str] = Field( + None, + description="Name of the current owner of the copyright of the artwork or object.", + title="Current Copyright Owner Name {Artwork or Object detail}", + ) + currentCopyrightOwnerIdentifier: Optional[AnyUrl] = Field( + None, + description="Globally unique identifier for the current owner of the copyright of the artwork or object.", + title="Current Copyright Owner ID {Artwork or Object detail}", + ) + copyrightNotice: Optional[str] = Field( + None, + description=( + "Contains any necessary copyright notice for claiming the intellectual property for artwork or an object in" + " the image and should identify the current owner of the copyright of this work with associated" + " intellectual property rights." + ), + title="Copyright Notice {Artwork or Object detail}", + ) + currentLicensorName: Optional[str] = Field( + None, + description="Name of the current licensor of the artwork or object.", + title="Current Licensor Name {Artwork or Object detail}", + ) + currentLicensorIdentifier: Optional[AnyUrl] = Field( + None, + description="Globally unique identifier for the current licensor of the artwork or object.", + title="Current Licensor ID {Artwork or Object detail}", + ) + + +class CreatorContactInfo(SchemaBaseModel): + class Config: + extra = Extra.forbid + + country: Optional[str] = Field( + None, description="The contact information country part.", title="Country {contact info detail}" + ) + emailwork: Optional[str] = Field( + None, description="The contact information email address part.", title="Email address(es) {contact info detail}" + ) + region: Optional[str] = Field( + None, + description="The contact information part denoting regional information such as state or province.", + title="State/Province {contact info detail}", + ) + phonework: Optional[str] = Field( + None, description="The contact information phone number part.", title="Phone number(s) {contact info detail}" + ) + weburlwork: Optional[str] = Field( + None, + description=( + "The contact information web address part. Multiple addresses can be given. May have to be separated by a" + " comma in the user interface." + ), + title="Web URL(s) {contact info detail}", + ) + address: Optional[str] = Field( + None, + description=( + "The contact information address part. Comprises an optional company name and all required information to" + " locate the building or postbox to which mail should be sent. To that end, the address is a multiline" + " field." + ), + title="Address {contact info detail}", + ) + city: Optional[str] = Field( + None, description="The contact information city part.", title="City {contact info detail}" + ) + postalCode: Optional[str] = Field( + None, + description="The contact information part denoting the local postal code.", + title="Postal Code {contact info detail}", + ) + + +class CvTerm(SchemaBaseModel): + class Config: + extra = Extra.forbid + + cvId: Optional[AnyUrl] = Field( + None, + description="The globally unique identifier of the Controlled Vocabulary the term is from.", + title="CV-Term CV ID {CV-Term detail}", + ) + cvTermName: Optional[str] = Field( + None, + description="The natural language name of the term from a Controlled Vocabulary.", + title="CV-Term name {CV-Term detail}", + ) + cvTermId: Optional[AnyUrl] = Field( + None, + description="The globally unique identifier of the term from a Controlled Vocabulary.", + title="CV-Term ID {CV-Term detail}", + ) + cvTermRefinedAbout: Optional[AnyUrl] = Field( + None, + description="The refined 'about' relationship of the term with the content.", + title="Refined 'about' Relationship of the CV-Term {CV-Term detail}", + ) + + +class Device(SchemaBaseModel): + class Config: + extra = Extra.forbid + + manufacturer: Optional[str] = Field(None, description="Name of the manufacturer of the device") + modelName: Optional[str] = Field(None, description="Name of the device model") + serialNumber: Optional[str] = Field(None, description="Serial number, assigned by manufacturer") + attLensDescription: Optional[str] = Field( + None, description="Short description of the lens used with the device at the time of the recording" + ) + ownerDeviceId: Optional[str] = Field(None, description="Identifier assigned by the owner of the device") + + +class EmbdEncRightsExpr(SchemaBaseModel): + class Config: + extra = Extra.forbid + + encRightsExpr: str = Field( + ..., + description=( + "Embedded serialized rights expression using a rights expression language which is encoded as a string." + ), + title="Encoded Rights Expression", + ) + rightsExprEncType: str = Field( + ..., + description="Encoding type of the rights expression, identified by an IANA Media Type.", + title="Encoding type", + ) + rightsExprLangId: AnyUrl = Field( + ..., + description="Identifier of the rights expression language used by the rights expression.", + title="Rights Expression Language ID", + ) + + +class Entity(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, description="Full name of the entity/concept", title="Name") + identifiers: Optional[List[AnyUrl]] = Field( + None, description="Globally unique identifier of the entity/concept", title="Identifier" + ) + + +class EntityWRole(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, description="Full name of the entity/concept", title="Name") + role: Optional[List[AnyUrl]] = Field( + None, description="Identifier of the role the entity has in the context of the metadata property", title="Role" + ) + identifiers: Optional[List[AnyUrl]] = Field( + None, description="Globally unique identifier of the entity/concept", title="Identifier" + ) + + +class EpisodeSeason(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, description="Name of the episode or season of a series", title="Name") + identifier: Optional[AnyUrl] = Field( + None, description="Identifier of the episode or season of a series", title="Identifier" + ) + number: Optional[float] = Field(None, description="Number of the episode or season of a series", title="Number") + + +class FrameSize(SchemaBaseModel): + class Config: + extra = Extra.forbid + + heightPixels: Optional[int] = Field(None, description="Height of the video frame in pixels", title="Height") + widthPixels: Optional[int] = Field(None, description="Width of the video frame in pixels", title="Width") + + +class LinkedEncRightsExpr(SchemaBaseModel): + class Config: + extra = Extra.forbid + + linkedRightsExpr: AnyUrl = Field( + ..., + description="Link to a rights expression using a rights expression language.", + title="Link to Encoded Rights Expression", + ) + rightsExprEncType: str = Field( + ..., + description="Encoding type of the rights expression, identified by an IANA Media Type.", + title="Encoding type", + ) + rightsExprLangId: AnyUrl = Field( + ..., + description="Identifier of the rights expression language used by the rights expression.", + title="Rights Expression Language ID", + ) + + +class Location(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, description="Full name of the location", title="Name") + identifiers: Optional[List[AnyUrl]] = Field( + None, description="Globally unique identifier of the location", title="Identifier" + ) + worldRegion: Optional[str] = Field( + None, description="Name of the world region the Location is located in", title="World region name" + ) + countryName: Optional[str] = Field( + None, description="Name of the country the Location is located in", title="Country name" + ) + countryCode: Optional[str] = Field( + None, description="ISO code of the country the Location is located in", title="Country ISO code" + ) + provinceState: Optional[str] = Field( + None, description="Name of the state or province the Location is located in", title="State/Province name" + ) + city: Optional[str] = Field(None, description="Name of the city the Location is located in", title="City name") + sublocation: Optional[str] = Field( + None, description="Name of a sub location the Location is located in", title="Sublocation name" + ) + gpsAltitude: Optional[float] = Field( + None, description="Altitude in meters of a WGS84 based position of this Location", title="GPS-Altitude" + ) + gpsLatitude: Optional[float] = Field( + None, description="Lattitude of a WGS84 based position of this Location", title="GPS-Lattitude" + ) + gpsLongitude: Optional[float] = Field( + None, description="Longitude of a WGS84 based position of this Location", title="GPS-Longitude" + ) + + +class PersonWDetails(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, description="Name of the person", title="Name") + description: Optional[str] = Field(None, description="A textual description of the person", title="Description") + identifiers: Optional[List[AnyUrl]] = Field( + None, description="Globally unique identifier of the person", title="Identifier" + ) + characteristics: Optional[List[CvTerm]] = Field( + None, description="A property or trait of the person", title="Characteristics" + ) + + +class Product(SchemaBaseModel): + class Config: + extra = Extra.forbid + + description: Optional[str] = Field( + None, description="A textual description of the product.", title="Description {Product detail}" + ) + gtin: str = Field( + ..., + description="A 14 digit GTIN (Global Trade Item Number) of the product (GTIN-8 to GTIN-14 codes are used).", + title="GTIN {Product detail}", + ) + name: Optional[str] = Field(None, description="Name of the product.", title="Name {Product detail}") + + +class ProductWGtin(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, description="Name of the product.", title="Name") + gtin: str = Field( + ..., + description="A 14 digit GTIN (Global Trade Item Number) of the product (GTIN-8 to GTIN-14 codes are used).", + title="GTIN", + ) + description: Optional[str] = Field(None, description="A textual description of the product.", title="Description") + + +class PublicationEvent(SchemaBaseModel): + class Config: + extra = Extra.forbid + + date: datetime = Field( + ..., description="Date and optionally the time of publishing the video", title="Publication Date" + ) + name: Optional[str] = Field( + None, description="Name of the event for publishing this video.", title="Publication Event Name" + ) + identifier: Optional[AnyUrl] = Field( + None, description="Identifier of the event for publishing this video", title="Publication Event Identifier" + ) + + +class QualifiedLink(SchemaBaseModel): + class Config: + extra = Extra.forbid + + link: Optional[AnyUrl] = Field(None, description="URL of the link", title="Link") + linkQualifier: Optional[AnyUrl] = Field(None, description="Term qualifying the use of the link", title="Qualifier") + + +class Rating(SchemaBaseModel): + class Config: + extra = Extra.forbid + + ratingSourceLink: AnyUrl = Field( + ..., + description=( + "Link to the site and optionally the page of the party which has issued the rating value, linked resource" + " should explain the rating rules." + ), + title="Rating Source Link", + ) + ratingValue: str = Field(..., description="Rating value as issued by the rating source", title="Rating Value") + ratingScaleMinValue: Optional[str] = Field( + None, + description="The value of the rating scale used for the lowest/worst rating", + title="Rating Scale Min Value", + ) + ratingRegions: Optional[List[Location]] = Field( + None, description="Geopolitical region to which this rating applies.", title="Rating Region" + ) + ratingValueLogoLink: Optional[AnyUrl] = Field( + None, description="Visualisation of the rating value referenced by a link", title="Rating Value Logo" + ) + ratingScaleMaxValue: Optional[str] = Field( + None, + description="The value of the rating scale used for the highest/best rating", + title="Rating Scale Max Value", + ) + + +class MeasureType(Enum): + """ + How the measures of the rectangle are expressed + """ + + dummy1 = "dummy1" + + +class RegionWDelimiter(SchemaBaseModel): + class Config: + extra = Extra.forbid + + regionAreaX: Optional[float] = Field( + None, + description="Horizontal axis value of the upper left corner of the rectange", + title="Rectangle, upper left X", + ) + regionAreaY: Optional[float] = Field( + None, + description="Vertical axis value of the upper left corner of the rectange", + title="Rectangle, upper left Y", + ) + measureType: Optional[MeasureType] = Field( + None, description="How the measures of the rectangle are expressed", title="Measure Type" + ) + regionAreaHeight: Optional[float] = Field( + None, description="Vertical height of the rectangle", title="Rectangle, vertical size" + ) + regionText: str = Field(..., description="Text or textual data", title="Text") + regionAreaWidth: Optional[float] = Field( + None, description="Horizontal width of the rectangle", title="Rectangle, horizontal size" + ) + + +class RegistryEntry(SchemaBaseModel): + class Config: + extra = Extra.forbid + + role: Optional[AnyUrl] = Field( + None, description="An identifier of the reason and/or purpose for this Registry Entry.", title="Role" + ) + assetIdentifier: str = Field( + ..., description="Unique identifier of the video as issued by a registry", title="Asset Identifier" + ) + registryIdentifier: AnyUrl = Field( + ..., + description="An identifier for the registry which issued the identifier of the video.", + title="Registry Identifier", + ) + + +class Series(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, description="Name of the series", title="Series name") + identifier: Optional[AnyUrl] = Field(None, description="Identifier for the series", title="Series identifier") + + +class TemporalCoverage(SchemaBaseModel): + class Config: + extra = Extra.forbid + + tempCoverageFrom: Optional[datetime] = Field( + None, description="Optionally truncated date when the temporal coverage starts", title="From Date" + ) + tempCoverageTo: Optional[datetime] = Field( + None, description="Optionally truncated date when the temporal coverage ends", title="To Date" + ) + + +class TimeFormat(Enum): + """ + Identifier of the time format. For time code formats following SMPTE specifications. + """ + + dummy1 = "dummy1" + + +class VideoTime(SchemaBaseModel): + """ + Frame of the video used for this still image + """ + + class Config: + extra = Extra.forbid + + timeValue: str = Field( + ..., + description=( + "Formated string including hours, minutes, seconds, fractions of seconds or frames, depending on the Time" + " Format" + ), + title="Time Value", + ) + timeFormat: TimeFormat = Field( + ..., + description="Identifier of the time format. For time code formats following SMPTE specifications.", + title="Time Format", + ) + + +class XmpSequence(SchemaBaseModel): + """ + Reflects the structure of an rdf:Seq in XMP/XML + """ + + class Config: + extra = Extra.forbid + + Ordered: Optional[List[Dict[str, Any]]] = None + + +class Topic(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Topic") + parent_id: Optional[str] = Field( + None, description="For subtopics, provide the ID of the parent topic", title="Parent topic Identifier" + ) + vocabulary: Optional[str] = Field( + None, description="Name of the controlled vocabulary, if the topic is from a taxonomy.", title="Vocabulary" + ) + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the topic is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Keyword(SchemaBaseModel): + name: str = Field(..., title="Name") + vocabulary: Optional[str] = Field(None, title="Vocabulary name") + uri: Optional[str] = Field(None, title="Vocabulary URI") + + +class CountryItem(SchemaBaseModel): + name: str = Field(..., title="Country name") + code: Optional[str] = Field(None, title="Country code") + + +class Gps(SchemaBaseModel): + """ + Geographic location of where the photo was taken + """ + + latitude: Optional[confloat(ge=-90.0, le=90.0)] = None + longitude: Optional[confloat(ge=-180.0, le=180.0)] = None + altitude: Optional[float] = None + + +class Language(SchemaBaseModel): + name: str = Field(..., title="Name") + code: Optional[str] = Field(None, title="Code") + + +class Type(Enum): + isPartOf = "isPartOf" + hasPart = "hasPart" + isVersionOf = "isVersionOf" + isFormatOf = "isFormatOf" + hasFormat = "hasFormat" + references = "references" + isReferencedBy = "isReferencedBy" + isBasedOn = "isBasedOn" + isBasisFor = "isBasisFor" + requires = "requires" + isRequiredBy = "isRequiredBy" + + +class Relation(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + type: Optional[Type] = Field(None, title="Type") + uri: Optional[str] = Field(None, description="Link to related resource", title="URI") + + +class DcmiSchema(SchemaBaseModel): + """ + Schema based on DCMI elements + """ + + type: Optional[str] = Field("image", description="Type of resource e.g. image", title="Type of resource") + title: str = Field(..., title="Photo title") + caption: Optional[str] = Field(None, title="Photo caption") + description: Optional[str] = Field(None, description="Description") + topics: Optional[List[Topic]] = Field(None, title="Topics") + keywords: Optional[List[Keyword]] = None + creator: Optional[str] = Field(None, description="Name of the person or organization", title="Creator") + contributor: Optional[str] = Field( + None, description="Name of the contributing person or organization", title="Contributor" + ) + publisher: Optional[str] = Field(None, title="Publisher") + date: Optional[str] = Field(None, description="Date when the photo was taken. use format YYYY-MM-DD", title="Date") + country: Optional[List[CountryItem]] = Field(None, title="Country") + coverage: Optional[str] = Field(None, title="Geographic coverage") + gps: Optional[Gps] = Field( + None, description="Geographic location of where the photo was taken", title="Geographic location" + ) + format: Optional[str] = Field( + None, + description=( + "Image file format e.g. `image/gif` - Image, GIF \n* `image/jpeg` - Image, JPEG \n* `image/png` - Image," + " PNG \n* `image/tiff` - Image, TIFF" + ), + title="Image format", + ) + languages: Optional[List[Language]] = Field( + None, description="Metadata language e.g. English, French, etc.", title="Language" + ) + relations: Optional[List[Relation]] = Field(None, title="Related resources") + rights: Optional[str] = Field(None, description="Copyrights", title="Rights") + source: Optional[str] = Field(None, description="Related resource from which resource is derived", title="Source") + note: Optional[str] = Field( + None, description="Any additional information not covered by other fields", title="Notes" + ) + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class PhotoVideoMetadataIPTC(SchemaBaseModel): + """ + Container for IPTC photo/video metadata + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field( + None, + description=( + "A shorthand reference for the digital image. Title provides a short human readable name which can be a" + " text and/or numeric reference. It is not the same as Headline." + ), + title="Title", + ) + imageSupplierImageId: Optional[str] = Field( + None, + description="Optional identifier assigned by the Image Supplier to the image.", + title="Image Supplier Image ID", + ) + registryEntries: Optional[List[RegistryEntry]] = Field( + None, + description=( + "Both a Registry Item Id and a Registry Organisation Id to record any registration of this digital image" + " with a registry." + ), + title="Image Registry Entry", + ) + digitalImageGuid: Optional[str] = Field( + None, + description=( + "Globally unique identifier for this digital image. It is created and applied by the creator of the digital" + " image at the time of its creation . This value shall not be changed after that time." + ), + title="Digital Image GUID", + ) + dateCreated: Optional[datetime] = Field( + None, + description=( + "Designates the date and optionally the time the content of the image was created rather than the date of" + " the creation of the digital representation." + ), + title="Date Created", + ) + headline: Optional[str] = Field( + None, description="A brief synopsis of the caption. Headline is not the same as Title.", title="Headline" + ) + eventName: Optional[str] = Field( + None, description="Names or describes the specific event at which the photo was taken.", title="Event" + ) + description: Optional[str] = Field( + None, description="A textual description, including captions, of the image.", title="Description" + ) + captionWriter: Optional[str] = Field( + None, + description=( + "Identifier or the name of the person involved in writing, editing or correcting the description of the" + " image." + ), + title="Description Writer", + ) + keywords: Optional[List[str]] = Field( + None, + description=( + "Keywords to express the subject of the image. Keywords may be free text and don't have to be taken from a" + " controlled vocabulary. Codes from the controlled vocabulary IPTC Subject NewsCodes must go to the" + ' "Subject Code" field.' + ), + title="Keywords", + ) + sceneCodes: Optional[List[str]] = Field( + None, + description=( + 'Describes the scene of a photo content. Specifies one ore more terms from the IPTC "Scene-NewsCodes". Each' + " Scene is represented as a string of 6 digits in an unordered list." + ), + title="Scene Code", + ) + sceneCodesLabelled: Optional[List[SceneCodesLabelledItem]] = Field( + None, + description=( + 'Describes the scene of a photo content. Specifies one ore more terms from the IPTC "Scene-NewsCodes". Each' + " Scene is represented as a string of 6 digits in an unordered list." + ), + title="Scene Codes", + ) + subjectCodes: Optional[List[str]] = Field( + None, + description=( + "Specifies one or more Subjects from the IPTC Subject-NewsCodes taxonomy to categorise the image. Each" + " Subject is represented as a string of 8 digits in an unordered list." + ), + title="Subject Code", + ) + subjectCodesLabelled: Optional[List[SubjectCodesLabelledItem]] = Field( + None, + description=( + "Specifies one or more Subjects from the IPTC Subject-NewsCodes taxonomy to categorise the image. Each" + " Subject is represented as a string of 8 digits in an unordered list." + ), + title="Subject Codes", + ) + creatorNames: Optional[List[str]] = Field( + None, + description=( + "Contains the name of the photographer, but in cases where the photographer should not be identified the" + " name of a company or organisation may be appropriate." + ), + title="Creator", + ) + creatorContactInfo: Optional[CreatorContactInfo] = Field( + None, + description=( + "The creator's contact information provides all necessary information to get in contact with the creator of" + " this image and comprises a set of sub-properties for proper addressing." + ), + title="Creator's Contact Info", + ) + creditLine: Optional[str] = Field( + None, + description=( + "The credit to person(s) and/or organisation(s) required by the supplier of the image to be used when" + " published. This is a free-text field." + ), + title="Credit Line", + ) + digitalSourceType: Optional[AnyUrl] = Field( + None, description="The type of the source of this digital image", title="Digital Source Type" + ) + jobid: Optional[str] = Field( + None, + description=( + "Number or identifier for the purpose of improved workflow handling. This is a user created identifier" + " related to the job for which the image is supplied." + ), + title="Job Id", + ) + jobtitle: Optional[str] = Field( + None, + description=( + "Contains the job title of the photographer. As this is sort of a qualifier the Creator element has to be" + " filled in as mandatory prerequisite for using Creator's Jobtitle." + ), + title="Creator's jobtitle", + ) + source: Optional[str] = Field( + None, + description=( + "The name of a person or party who has a role in the content supply chain. This could be an agency, a" + " member of an agency, an individual or a combination. Source could be different from Creator and from the" + " entities in the Copyright Notice." + ), + title="Source", + ) + locationsShown: Optional[List[Location]] = Field( + None, description="The location the photo was taken.", title="Location created" + ) + imageRating: Optional[int] = Field(None, description="Rating of the image by its user or supplier") + supplier: Optional[List[Entity]] = Field( + None, + description="Identifies the most recent supplier of the image, who is not necessarily its owner or creator.", + title="Image Supplier", + ) + copyrightNotice: Optional[str] = Field( + None, + description=( + "Contains any necessary copyright notice for claiming the intellectual property for this photograph and" + " should identify the current owner of the copyright for the photograph. Other entities like the creator of" + " the photograph may be added in the corresponding field. Notes on usage rights should be provided in" + ' "Rights usage terms".' + ), + title="Copyright Notice", + ) + copyrightOwners: Optional[List[EntityWRole]] = Field( + None, description="Owner or owners of the copyright in the licensed image.", title="Copyright Owner" + ) + usageTerms: Optional[str] = Field( + None, description="The licensing parameters of the image expressed in free-text.", title="Rights Usage Terms" + ) + embdEncRightsExpr: Optional[List[EmbdEncRightsExpr]] = Field( + None, + description="An embedded rights expression using any rights expression language", + title="Embedded Encoded Rights Expression", + ) + linkedEncRightsExpr: Optional[List[LinkedEncRightsExpr]] = Field( + None, + description="A linked rights expression using any rights expression language.", + title="Linked Encoded Rights Expression", + ) + webstatementRights: Optional[AnyUrl] = Field( + None, + description=( + "URL referencing a web resouce providing a statement of the copyright ownership and usage rights of the" + " image." + ), + ) + instructions: Optional[str] = Field( + None, + description=( + "Any of a number of instructions from the provider or creator to the receiver of the image which might" + " include any of the following: embargoes (NewsMagazines OUT) and other restrictions not covered by the" + ' "Rights Usage Terms" field; information regarding the original means of capture (scanning notes,' + " colourspace info) or other specific text information that the user may need for accurate reproduction;" + " additional permissions required when publishing; credits for publishing if they exceed the IIM length of" + " the credit field" + ), + title="Instructions", + ) + genres: Optional[List[CvTerm]] = Field( + None, + description=( + "Artistic, style, journalistic, product or other genre(s) of the image (expressed by a term from any" + " Controlled Vocabulary)" + ), + title="Genre", + ) + intellectualGenre: Optional[str] = Field( + None, + description="Describes the nature, intellectual, artistic or journalistic characteristic of an image.", + title="Intellectual Genre", + ) + artworkOrObjects: Optional[List[ArtworkOrObject]] = Field( + None, + description="A set of metadata about artwork or an object in the image", + title="Artwork or Object in the Image", + ) + personInImageNames: Optional[List[str]] = Field( + None, description="Name of a person shown in the image.", title="Person Shown in the Image" + ) + personsShown: Optional[List[PersonWDetails]] = Field( + None, description="Details about a person the content is about.", title="Person Shown in the Image with Details" + ) + modelAges: Optional[List[int]] = Field( + None, + description="Age of the human model(s) at the time this image was taken in a model released image.", + title="Model Age", + ) + additionalModelInfo: Optional[str] = Field( + None, + description="Information about the ethnicity and other facets of the model(s) in a model-released image.", + title="Additional Model Information", + ) + minorModelAgeDisclosure: Optional[AnyUrl] = Field( + None, + description="Age of the youngest model pictured in the image, at the time that the image was made.", + title="Minor Model Age Disclosure", + ) + modelReleaseDocuments: Optional[List[str]] = Field( + None, description="Optional identifier associated with each Model Release.", title="Model Release Id" + ) + modelReleaseStatus: Optional[CvTerm] = Field( + None, + description=( + "Summarises the availability and scope of model releases authorising usage of the likenesses of persons" + " appearing in the photograph." + ), + title="Model Release Status", + ) + organisationInImageCodes: Optional[List[str]] = Field( + None, + description=( + "Code from a controlled vocabulary for identifying the organisation or company which is featured in the" + " image." + ), + title="Code of Organisation Featured in the Image", + ) + organisationInImageNames: Optional[List[str]] = Field( + None, + description="Name of the organisation or company which is featured in the image.", + title="Name of Organisation Featured in the Image", + ) + productsShown: Optional[List[Product]] = Field( + None, description="A product the content is about.", title="Product Shown in the Image" + ) + maxAvailHeight: Optional[int] = Field( + None, + description=( + "The maximum available height in pixels of the original photo from which this photo has been derived by" + " downsizing." + ), + title="Max Avail Height", + ) + maxAvailWidth: Optional[int] = Field( + None, + description=( + "The maximum available width in pixels of the original photo from which this photo has been derived by" + " downsizing." + ), + title="Max Avail Width", + ) + propertyReleaseStatus: Optional[CvTerm] = Field( + None, + description=( + "Summarises the availability and scope of property releases authorising usage of the properties appearing" + " in the photograph." + ), + title="Property Release Status", + ) + propertyReleaseDocuments: Optional[List[str]] = Field( + None, description="Optional identifier associated with each Property Release.", title="Property Release Id" + ) + aboutCvTerms: Optional[List[CvTerm]] = Field( + None, + description=( + "One or more topics, themes or entities the content is about, each one expressed by a term from a" + " Controlled Vocabulary." + ), + title="CV-Term About Image", + ) + + +class IptcPmdSchema(SchemaBaseModel): + """ + Overall structure of photo metadata of a single media asset - sets of metadata for the whole asset and parts of the asset -- the properties comply with the IPTC Photo Metadata Standard 2017.1(IPTC/MS/2017-07-06) + """ + + class Config: + extra = Extra.forbid + + photoVideoMetadataIPTC: PhotoVideoMetadataIPTC = Field(..., description="Container for IPTC photo/video metadata") + + +class LinkedImage(SchemaBaseModel): + class Config: + extra = Extra.forbid + + link: AnyUrl = Field(..., description="Link URL locating the image resource") + mediaType: Optional[str] = Field(None, description="IANA Media (MIME) Type") + widthPixels: Optional[int] = Field(None, description="Width of the image in pixels") + heightPixels: Optional[int] = Field(None, description="Height of the image in pixels") + role: Optional[str] = Field(None, description="Role of this image in the context of the video") + linkQualifiers: Optional[List[AnyUrl]] = Field( + None, description="Qualifier of the relationship of the image with the video" + ) + usedVideoFrame: Optional[VideoTime] = None + + +class ImageDescription(SchemaBaseModel): + idno: Optional[str] = Field(None, description="Image unique identifier", title="Image unique identifier") + identifiers: Optional[List[Identifier]] = Field(None, description="Other identifiers", title="Other identifiers") + iptc: Optional[IptcPmdSchema] = None + dcmi: Optional[DcmiSchema] = None + license: Optional[List[LicenseItem]] = Field(None, title="License") + album: Optional[List[AlbumItem]] = Field(None, title="Album") + + +class ImageDataTypeSchema(SchemaBaseModel): + """ + Uses IPTC JSON schema. See for more details - http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata. + """ + + repositoryid: Optional[str] = Field( + "central", + description="Abbreviation for the collection that owns the document", + title="Collection ID that owns the document", + ) + published: Optional[int] = Field("0", description="Status `0=draft`, `1=published`", title="Status") + overwrite: Optional[Overwrite] = Field("no", description="Overwrite document if already exists?") + metadata_information: Optional[MetadataInformation] = Field( + None, description="Document description", title="Document metadata information" + ) + image_description: Optional[ImageDescription] = None + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags") + lda_topics: Optional[List[LdaTopic]] = Field(None, description="LDA topics", title="LDA topics") + embeddings: Optional[List[Embedding]] = Field(None, description="Word embeddings", title="Word embeddings") + additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata") diff --git a/pydantic_schemas/indicator_schema.py b/pydantic_schemas/indicator_schema.py new file mode 100644 index 0000000..7ce366b --- /dev/null +++ b/pydantic_schemas/indicator_schema.py @@ -0,0 +1,734 @@ +# generated by datamodel-codegen: +# filename: timeseries-schema.json +# timestamp: 2024-09-13T19:00:32+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import Extra, Field + +from .utils.schema_base_model import SchemaBaseModel + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class VersionStatement(SchemaBaseModel): + """ + Version Statement + """ + + version: Optional[str] = Field(None, title="Version") + version_date: Optional[str] = Field(None, title="Version Date") + version_resp: Optional[str] = Field( + None, + description="The organization or person responsible for the version of the work", + title="Version Responsibility Statement", + ) + version_notes: Optional[str] = Field(None, title="Version Notes") + + +class MetadataInformation(SchemaBaseModel): + """ + Information on the production of the metadata + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + prod_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version_statement: Optional[VersionStatement] = Field( + None, description="Version Statement", title="Version Statement" + ) + + +class AuthoringEntityItem(SchemaBaseModel): + name: str = Field( + ..., + description=( + "Name of the person, corporate body, or agency responsible for the work's substantive and intellectual" + " content. If a person, invert first and last name and use commas." + ), + title="Agency Name", + ) + affiliation: Optional[str] = Field(None, title="Affiliation") + abbreviation: Optional[Any] = Field(None, description="Abbreviation", title="Abbreviation") + email: Optional[Any] = Field(None, description="Email", title="Email") + uri: Optional[str] = Field(None, title="URI") + + +class Alias(SchemaBaseModel): + alias: Optional[str] = Field(None, title="Alias") + + +class AlternateIdentifier(SchemaBaseModel): + identifier: str = Field(..., title="Identifier") + name: Optional[str] = Field(None, title="Identifier name") + database: Optional[str] = Field(None, title="Database") + uri: Optional[str] = Field(None, title="URI") + notes: Optional[str] = Field(None, title="Notes") + + +class Language(SchemaBaseModel): + name: Optional[str] = Field(None, description="Language title", title="Name") + code: Optional[str] = Field(None, title="code") + + +class CodeListItem(SchemaBaseModel): + code: Optional[str] = Field(None, title="Code") + label: Optional[str] = Field(None, title="Label") + description: Optional[str] = Field(None, title="Description") + + +class Dimension(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + label: str = Field(..., title="Label") + description: Optional[str] = Field(None, title="Description") + code_list: Optional[List[CodeListItem]] = Field(None, title="Code list") + + +class DefinitionReference(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + uri: Optional[str] = Field(None, description="URI", title="URI") + note: Optional[str] = Field(None, description="Note", title="Note") + + +class StatisticalConceptReference(DefinitionReference): + pass + + +class Concept(SchemaBaseModel): + name: str = Field(..., title="Name") + definition: Optional[str] = Field(None, description="Definition", title="Definition") + uri: Optional[str] = Field(None, description="Website link", title="URI") + + +class DataCollection(SchemaBaseModel): + """ + This description should include, when applicable, the sample frame used, the questions used to collect the data, the type of interview, the dates/duration of fieldwork, the sample size and the response rate. Some additional information on questionnaire design and testing, interviewer training, methods used to monitor non-response etc. + """ + + data_source: Optional[str] = Field(None, title="Data source") + method: Optional[str] = Field(None, title="Data collection method") + period: Optional[str] = Field(None, title="Data collection period") + note: Optional[str] = Field(None, title="Data collection note") + uri: Optional[str] = Field(None, title="Data collection URL") + + +class MethodologyReference(DefinitionReference): + pass + + +class DerivationReference(DefinitionReference): + pass + + +class ImputationReference(DefinitionReference): + pass + + +class Theme(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Name") + parent_id: Optional[str] = Field(None, title="Parent Identifier") + vocabulary: Optional[str] = Field(None, description="Name of the controlled vocabulary", title="Vocabulary") + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the theme is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Topic(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Topic") + parent_id: Optional[str] = Field( + None, description="For subtopics, provide the ID of the parent topic", title="Parent topic Identifier" + ) + vocabulary: Optional[str] = Field( + None, description="Name of the controlled vocabulary, if the topic is from a taxonomy.", title="Vocabulary" + ) + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the topic is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Discipline(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Discipline title or name") + parent_id: Optional[str] = Field(None, title="Parent Identifier") + vocabulary: Optional[str] = Field(None, description="Vocabulary", title="Vocabulary") + uri: Optional[str] = Field(None, description="Website link", title="URI") + + +class Mandate(SchemaBaseModel): + """ + Mandate + """ + + mandate: Optional[str] = Field(None, title="Mandate") + uri: Optional[str] = Field(None, title="URL") + + +class TimePeriod(SchemaBaseModel): + start: Optional[str] = Field(None, title="Start") + end: Optional[str] = Field(None, title="End") + notes: Optional[str] = Field(None, title="Notes") + + +class RefCountryItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Country name") + code: Optional[str] = Field(None, title="Country code") + + +class GeographicUnit(SchemaBaseModel): + name: str = Field( + ..., description="Name of the geographic unit e.g. 'World', 'Africa', 'Afghanistan'", title="Location name" + ) + code: Optional[str] = Field( + None, description="Code of the geographic unit (for countries, preferred = ISO3 code)", title="Location code" + ) + type: Optional[str] = Field( + None, description="Type of geographic unit e.g. country, state, region, province etc", title="Type" + ) + + +class BboxItem(SchemaBaseModel): + west: Optional[str] = Field(None, title="West") + east: Optional[str] = Field(None, title="East") + south: Optional[str] = Field(None, title="South") + north: Optional[str] = Field(None, title="North") + + +class AggregationMethodReference(DefinitionReference): + pass + + +class LicenseItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + uri: Optional[str] = Field(None, title="URI") + note: Optional[str] = Field(None, title="Note") + + +class Link(SchemaBaseModel): + type: Optional[str] = Field(None, description="Link types - API, website, etc.", title="Link type") + description: Optional[str] = Field(None, title="Description") + uri: Optional[str] = Field(None, title="URI") + + +class ApiDocumentationItem(SchemaBaseModel): + description: Optional[str] = Field(None, title="Description") + uri: Optional[str] = Field(None, title="URI") + + +class OtherIdentifier(SchemaBaseModel): + type: Optional[str] = Field(None, title="Type") + identifier: Optional[str] = Field(None, title="Identifier") + + +class AuthorIdItem(SchemaBaseModel): + type: Optional[str] = Field(None, title="Type") + id: Optional[str] = Field(None, title="Identifier") + + +class Author(SchemaBaseModel): + first_name: Optional[str] = Field(None, title="First name") + initial: Optional[str] = Field(None, title="Initial") + last_name: Optional[str] = Field(None, title="Last name") + affiliation: Optional[str] = Field(None, title="Affiliation") + author_id: Optional[List[AuthorIdItem]] = Field(None, title="Author ID") + full_name: Optional[str] = Field(None, title="Full name") + + +class Dataset(SchemaBaseModel): + idno: Optional[str] = Field(None, title="Identifier (IDNO)") + title: Optional[str] = Field( + None, description="Title of the dataset inluding the country and year if relevant", title="Title" + ) + uri: Optional[str] = Field(None, title="URI") + + +class Source(SchemaBaseModel): + idno: Optional[str] = Field(None, title="Source ID") + other_identifiers: Optional[List[OtherIdentifier]] = Field(None, title="Identifiers") + type: Optional[str] = Field(None, title="Source type") + name: str = Field(..., description="Source name", title="Name") + organization: Optional[str] = Field(None, title="Organization") + authors: Optional[List[Author]] = Field(None, title="Authors") + datasets: Optional[List[Dataset]] = Field(None, title="Datasets") + publisher: Optional[str] = Field(None, title="Publisher") + publication_date: Optional[str] = Field(None, title="Publication date") + uri: Optional[str] = Field(None, title="URI") + access_date: Optional[str] = Field(None, title="Access date") + note: Optional[str] = Field(None, title="Note") + + +class DirectSource(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + organization: Optional[str] = Field(None, title="Organization") + uri: Optional[str] = Field(None, title="URI") + note: Optional[str] = Field(None, title="Note") + + +class Keyword(SchemaBaseModel): + name: str = Field(..., title="Keyword") + vocabulary: Optional[str] = Field(None, title="Vocabulary") + uri: Optional[str] = Field(None, title="URI") + + +class Acronym(SchemaBaseModel): + acronym: str = Field(..., title="Acronym or abbreviation") + expansion: str = Field(..., title="Expansion of the acronym or abbreviation") + occurrence: Optional[float] = Field(None, title="Occurrence of the acronym in the document") + + +class Erratum(SchemaBaseModel): + date: Optional[str] = Field( + None, description="Date when the erratum was reported or published", title="Date of erratum" + ) + description: Optional[str] = Field( + None, + description="A description of the erratum, with information on which data or metadata were impacted", + title="Description of the erratum", + ) + uri: Optional[str] = Field(None, title="URI") + + +class Acknowledgement(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class Note(SchemaBaseModel): + note: Optional[str] = Field(None, title="Note") + type: Optional[str] = Field(None, description="Type of note", title="Note type") + uri: Optional[str] = Field(None, title="URI") + + +class RelatedIndicator(SchemaBaseModel): + id: Optional[str] = Field(None, description="Indicator unique identifier", title="Identifier") + code: Optional[str] = Field(None, title="Indicator code") + label: Optional[str] = Field(None, title="Indicator name") + uri: Optional[str] = Field(None, title="URI") + relationship: Optional[str] = Field(None, title="Relationship") + type: Optional[str] = Field(None, title="Type") + + +class ComplianceItem(SchemaBaseModel): + standard: str = Field(..., title="Standard name") + abbreviation: Optional[str] = Field(None, title="Abbreviation") + custodian: Optional[str] = Field(None, title="Name of the custodian organization") + uri: Optional[str] = Field(None, title="URI") + + +class FrameworkItem(SchemaBaseModel): + name: str = Field(..., title="Name") + abbreviation: Optional[str] = Field(None, title="Abbreviation") + custodian: Optional[str] = Field(None, title="Custodian") + description: Optional[str] = Field(None, title="Description") + goal_id: Optional[str] = Field(None, title="Goal ID") + goal_name: Optional[str] = Field(None, title="Goal name") + goal_description: Optional[str] = Field(None, title="Goal description") + target_id: Optional[str] = Field(None, title="target ID") + target_name: Optional[str] = Field(None, title="Target name") + target_description: Optional[str] = Field(None, title="Target description") + indicator_id: Optional[str] = Field(None, title="Indicator ID") + indicator_name: Optional[str] = Field(None, title="Indicator name") + indicator_description: Optional[str] = Field(None, title="Indicator description") + uri: Optional[str] = Field(None, title="URI") + notes: Optional[str] = Field(None, title="Description") + + +class SeriesGroup(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + description: Optional[str] = Field( + None, description="A brief description of the series group.", title="Description" + ) + version: Optional[str] = Field(None, title="Version") + uri: Optional[str] = Field(None, title="URI") + + +class Contact(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + role: Optional[str] = Field(None, title="Role") + position: Optional[str] = Field(None, title="Position") + affiliation: Optional[str] = Field(None, title="Affiliation") + email: Optional[str] = Field(None, title="Email") + telephone: Optional[str] = Field(None, title="Telephone") + uri: Optional[str] = Field(None, title="URI") + + +class SeriesDescription(SchemaBaseModel): + """ + Series information + """ + + idno: str = Field(..., description="Unique series ID", title="Series unique ID") + doi: Optional[str] = Field(None, title="DOI handle") + name: str = Field(..., title="Series Name") + display_name: Optional[str] = Field(None, title="Display Name") + authoring_entity: Optional[List[AuthoringEntityItem]] = Field( + None, + description=( + "The person, corporate body, or agency responsible for the work's substantive and intellectual content." + " Repeat the element for each author, and use 'affiliation' attribute if available. Invert first and last" + " name and use commas." + ), + title="Authoring entity", + ) + database_id: Optional[str] = Field(None, description="Series database ID", title="Database ID") + database_name: Optional[str] = Field(None, description="Series database name", title="Database name") + date_last_update: Optional[str] = Field(None, title="Last updated date") + date_released: Optional[str] = Field(None, title="Date released") + version_statement: Optional[VersionStatement] = Field( + None, description="Version Statement", title="Version Statement" + ) + aliases: Optional[List[Alias]] = Field(None, title="Series other names") + alternate_identifiers: Optional[List[AlternateIdentifier]] = Field(None, title="Alternate identifiers") + languages: Optional[List[Language]] = Field(None, description="Supported languages") + measurement_unit: Optional[str] = Field(None, title="Series unit of measure") + power_code: Optional[str] = Field( + None, + description=( + "Power of 10 by which the reported statistics should be multiplied. e.g. '6' indicating millions of units" + ), + title="Power code", + ) + dimensions: Optional[List[Dimension]] = Field(None, title="Dimensions") + release_calendar: Optional[str] = Field(None, description="Release calendar", title="Release calendar") + periodicity: Optional[str] = Field(None, title="Periodicity of data") + base_period: Optional[str] = Field(None, title="Base period") + definition_short: Optional[str] = Field(None, title="Definition short") + definition_long: Optional[str] = Field(None, title="Definition long") + definition_references: Optional[List[DefinitionReference]] = Field( + None, + description="URL to standard definition of the indicator (international or national standard)", + title="Definition references", + ) + statistical_concept: Optional[str] = Field(None, title="Statistical concept") + statistical_concept_references: Optional[List[StatisticalConceptReference]] = Field( + None, description="URLs for statistical concept references", title="Statistical concept references" + ) + concepts: Optional[List[Concept]] = Field(None, description="Related concepts", title="Related concepts") + universe: Optional[str] = Field( + None, + description="Target population (the statistical universe about which information is sought)", + title="Universe", + ) + data_collection: Optional[DataCollection] = Field( + None, + description=( + " This description should include, when applicable, the sample frame used, the questions used to collect" + " the data, the type of interview, the dates/duration of fieldwork, the sample size and the response rate." + " Some additional information on questionnaire design and testing, interviewer training, methods used to" + " monitor non-response etc." + ), + title="Data collection", + ) + methodology: Optional[str] = Field(None, title="Methodology") + methodology_references: Optional[List[MethodologyReference]] = Field( + None, description="URLs for methodology references", title="Methodology references" + ) + derivation: Optional[str] = Field(None, title="Derivation") + derivation_references: Optional[List[DerivationReference]] = Field( + None, description="URLs for derivation references", title="Derivation references" + ) + imputation: Optional[str] = Field(None, title="Imputations") + imputation_references: Optional[List[ImputationReference]] = Field( + None, description="URLs for imputation references", title="Imputation references" + ) + seasonal_adjustment: Optional[str] = Field( + None, + description=( + "Application of statistical techniques to time series data in order to remove seasonal fluctuations and to" + " better understand underlying trends." + ), + title="Seasonal adjustment", + ) + adjustments: Optional[List[str]] = Field( + None, + description=( + "Description of any adjustments with respect to use of standard classifications and harmonization of" + " breakdowns for age group and other dimensions, or adjustments made for compliance with specific" + " international or national definitions." + ), + title="Other adjustments", + ) + missing: Optional[str] = Field(None, title="Treatment of missing values") + validation_rules: Optional[List[str]] = Field( + None, description="Set of rules to validate values for indicators, e.g. range checks", title="Validation rules" + ) + quality_checks: Optional[str] = Field(None, title="Quality control methods") + quality_note: Optional[str] = Field(None, title="Note on data quality") + sources_discrepancies: Optional[str] = Field(None, title="Discrepency sources") + series_break: Optional[str] = Field(None, title="Breaks in series") + limitation: Optional[str] = Field(None, title="Limitations and exceptions") + themes: Optional[List[Theme]] = Field(None, description="Themes") + topics: Optional[List[Topic]] = Field( + None, + description="Topics covered by the table (ideally, the list of topics will be a controlled vocabulary)", + title="Topics", + ) + disciplines: Optional[List[Discipline]] = Field( + None, + description="Disciplines e.g. `Social sciences, economics`, `Natural sciences, biology`", + title="Disciplines", + ) + relevance: Optional[str] = Field(None, title="Relavance") + mandate: Optional[Mandate] = Field(None, description="Mandate", title="Mandate") + time_periods: Optional[List[TimePeriod]] = Field(None, title="Series dates") + ref_country: Optional[List[RefCountryItem]] = Field( + None, description="List of countries for which data are available", title="Reference country" + ) + geographic_units: Optional[List[GeographicUnit]] = Field( + None, + description=( + "List of geographic units (regions, countries, states, provinces, etc.) for which data are available in the" + " database." + ), + title="Geographic locations", + ) + bbox: Optional[List[BboxItem]] = Field(None, title="Geographic bounding box") + aggregation_method: Optional[str] = Field(None, title="Aggregation method") + aggregation_method_references: Optional[List[AggregationMethodReference]] = Field( + None, description="URLs for aggregation method references", title="Aggregation method references" + ) + disaggregation: Optional[str] = Field(None, title="Dissaggregation") + license: Optional[List[LicenseItem]] = Field(None, description="License information", title="License") + confidentiality: Optional[str] = Field( + None, description="Confidentiality statement", title="Confidentiality statement" + ) + confidentiality_status: Optional[str] = Field(None, title="Confidentiality status") + confidentiality_note: Optional[str] = Field(None, title="Confidentiality note") + citation_requirement: Optional[str] = Field( + None, description="Citation requirement (can include a specific recommended citation)" + ) + links: Optional[List[Link]] = Field(None, description="Links to API calls, websites, etc.", title="Series links") + api_documentation: Optional[List[ApiDocumentationItem]] = Field(None, description="API Documentation") + sources: Optional[List[Source]] = Field(None, description="Sources", title="Sources") + sources_note: Optional[str] = Field(None, title="Notes form original sources") + direct_sources: Optional[List[DirectSource]] = Field( + None, description="Refers to the sources from where the data was directly collected", title="Direct sources" + ) + keywords: Optional[List[Keyword]] = Field(None, description="Keywords") + acronyms: Optional[List[Acronym]] = Field(None, description="Acronyms") + errata: Optional[List[Erratum]] = Field( + None, description="List of corrected errors in data or metadata", title="Errata" + ) + acknowledgements: Optional[List[Acknowledgement]] = Field( + None, description="Acknowledgments of persons or organizations", title="Other acknowledgments" + ) + acknowledgement_statement: Optional[str] = Field( + None, description="Acknowledgement statement", title="Acknowledgement statement" + ) + disclaimer: Optional[str] = Field(None, title="Disclaimer") + notes: Optional[List[Note]] = Field(None, description="Notes", title="Notes") + related_indicators: Optional[List[RelatedIndicator]] = Field(None, description="Related indicators") + compliance: Optional[List[ComplianceItem]] = Field( + None, description="Compliance with international resolution", title="Compliance with international resolution" + ) + framework: Optional[List[FrameworkItem]] = Field(None, title="Framework") + series_groups: Optional[List[SeriesGroup]] = Field( + None, description="Series included in groups", title="Series groups" + ) + contacts: Optional[List[Contact]] = Field(None, description="Contacts", title="Contacts") + + +class DataType(Enum): + string = "string" + integer = "integer" + float = "float" + date = "date" + boolean = "boolean" + + +class ColumnType(Enum): + dimension = "dimension" + time_period = "time_period" + measure = "measure" + attribute = "attribute" + indicator_id = "indicator_id" + indicator_name = "indicator_name" + annotation = "annotation" + geography = "geography" + observation_value = "observation_value" + + +class TimePeriodFormat(Enum): + YYYY = "YYYY" + YYYY_MM = "YYYY-MM" + YYYY_MM_DD = "YYYY-MM-DD" + YYYY_MM_DDTHH_MM_SS = "YYYY-MM-DDTHH:MM:SS" + YYYY_MM_DDTHH_MM_SSZ = "YYYY-MM-DDTHH:MM:SSZ" + + +class CodeListReference(SchemaBaseModel): + id: Optional[str] = Field(None, title="Identifier (ID)") + name: Optional[str] = Field(None, title="Name") + version: Optional[str] = Field(None, title="Version") + uri: str = Field(..., description="URI", title="URI") + note: Optional[str] = Field(None, description="Note", title="Note") + + +class DataStructureItem(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + label: Optional[str] = Field(None, title="Label") + description: Optional[str] = Field(None, title="Description") + data_type: Optional[DataType] = Field(None, title="Data type") + column_type: Optional[ColumnType] = Field(None, title="Column type") + time_period_format: Optional[TimePeriodFormat] = Field(None, title="Time period format") + code_list: Optional[List[CodeListItem]] = Field(None, title="Code list") + code_list_reference: Optional[CodeListReference] = Field(None, title="Code list reference") + + +class Operator(Enum): + field_ = "=" + field__ = "!=" + field__1 = "<" + field___1 = "<=" + field__2 = ">" + field___2 = ">=" + in_ = "in" + field_in = "!in" + + +class Filter(SchemaBaseModel): + field: Optional[str] = Field(None, title="Field") + operator: Optional[Operator] = Field(None, title="Operator") + value: Optional[Union[str, float, bool, List[Any]]] = Field(None, title="Value") + + +class DataNote(SchemaBaseModel): + filters: Optional[List[Filter]] = Field(None, description="Filters", title="Filters") + note: Optional[str] = Field(None, title="Note") + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class NameType(Enum): + Personal = "Personal" + Organizational = "Organizational" + + +class Creator(SchemaBaseModel): + name: str = Field(..., title="Name") + nameType: Optional[NameType] = Field(None, title="Name type") + givenName: Optional[str] = Field(None, title="Given name") + familyName: Optional[str] = Field(None, title="Family name") + + +class TitleType(Enum): + AlternativeTitle = "AlternativeTitle" + Subtitle = "Subtitle" + TranslatedTitle = "TranslatedTitle" + Other = "Other" + + +class Title(SchemaBaseModel): + title: str = Field(..., title="Title") + titleType: Optional[TitleType] = Field(None, title="Title type") + lang: Optional[str] = Field(None, title="Language") + + +class ResourceTypeGeneral(Enum): + Audiovisual = "Audiovisual" + Collection = "Collection" + DataPaper = "DataPaper" + Dataset = "Dataset" + Event = "Event" + Image = "Image" + InteractiveResource = "InteractiveResource" + Model = "Model" + PhysicalObject = "PhysicalObject" + Service = "Service" + Software = "Software" + Sound = "Sound" + Text = "Text" + Workflow = "Workflow" + Other = "Other" + + +class Types(SchemaBaseModel): + resourceType: str = Field(..., title="Resource type") + resourceTypeGeneral: Optional[ResourceTypeGeneral] = Field(None, title="Resource type general") + + +class DataciteSchema(SchemaBaseModel): + """ + Schema based on Datacite elements + """ + + doi: Optional[str] = Field(None, title="DOI") + prefix: Optional[str] = Field(None, title="Prefix") + suffix: Optional[str] = Field(None, title="Suffix") + creators: Optional[List[Creator]] = Field(None, title="Creators") + titles: Optional[List[Title]] = Field(None, title="Titles") + publisher: Optional[str] = Field(None, title="Publisher") + publicationYear: Optional[str] = Field(None, title="Publication year") + types: Optional[Types] = Field(None, title="Types") + url: Optional[str] = Field(None, title="URL") + language: Optional[str] = Field(None, title="Language") + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class TimeseriesSchema(SchemaBaseModel): + """ + Schema for timeseries data type + """ + + idno: Optional[str] = Field(None, description="Project unique identifier", title="Project unique identifier") + metadata_information: Optional[MetadataInformation] = Field( + None, description="Information on the production of the metadata", title="Metadata creation" + ) + series_description: SeriesDescription = Field(..., description="Series information") + data_structure: Optional[List[DataStructureItem]] = Field(None, description="Data structure definition") + data_notes: Optional[List[DataNote]] = Field(None, description="Data notes", title="Data notes") + datacite: Optional[DataciteSchema] = Field(None, description="DataCite metadata for generating DOI") + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags (user-defined)") + additional: Optional[Dict[str, Any]] = Field( + None, description="Any other custom metadata not covered by the schema", title="Additional custom metadata" + ) diff --git a/pydantic_schemas/indicators_db_schema.py b/pydantic_schemas/indicators_db_schema.py new file mode 100644 index 0000000..67b860b --- /dev/null +++ b/pydantic_schemas/indicators_db_schema.py @@ -0,0 +1,403 @@ +# generated by datamodel-codegen: +# filename: timeseries-db-schema.json +# timestamp: 2024-09-13T19:00:30+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional + +from pydantic import Extra, Field + +from .utils.schema_base_model import SchemaBaseModel + + +class Overwrite(Enum): + """ + Overwrite database if already exists? + """ + + yes = "yes" + no = "no" + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class MetadataInformation(SchemaBaseModel): + """ + Document description + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + prod_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version: Optional[str] = Field( + None, description="Identify and describe the current version of the document", title="Document version" + ) + + +class Identifier(SchemaBaseModel): + type: Optional[str] = Field( + None, description="Type of identifier e.g. `doi`, `handle`, `other`", title="Identifier type" + ) + identifier: str = Field(..., title="Identifier") + + +class TitleStatement(SchemaBaseModel): + """ + Study title + """ + + idno: str = Field( + ..., + description="The ID number of a database is a unique number that is used to identify a particular database.", + title="Unique user defined ID", + ) + identifiers: Optional[List[Identifier]] = Field(None, description="Other identifiers", title="Other identifiers") + title: str = Field( + ..., + description=( + "The title is the official name of the survey as it is stated on the questionnaire or as it appears in the" + " design documents. The following items should be noted:\n - Include the reference year(s) of the survey in" + " the title. \n - Do not include the abbreviation of the survey name in the title. \n - As the survey title" + " is a proper noun, the first letter of each word should be capitalized (except for prepositions or other" + " conjunctions).\n - Including the country name in the title is optional." + ), + title="Survey title", + ) + sub_title: Optional[str] = Field(None, description="A short subtitle for the survey", title="Survey subtitle") + alternate_title: Optional[str] = Field( + None, + description=( + "The abbreviation of a survey is usually the first letter of each word of the titled survey. The survey" + " reference year(s) may be included." + ), + title="Abbreviation or Acronym", + ) + translated_title: Optional[str] = Field( + None, + description="In countries with more than one official language, a translation of the title may be provided.", + title="Translated Title", + ) + + +class AuthoringEntityItem(SchemaBaseModel): + name: str = Field( + ..., + description=( + "Name of the person, corporate body, or agency responsible for the work's substantive and intellectual" + " content. If a person, invert first and last name and use commas." + ), + title="Agency Name", + ) + affiliation: Optional[str] = Field(None, title="Affiliation") + abbreviation: Optional[str] = Field(None, description="Abbreviation", title="Abbreviation") + email: Optional[str] = Field(None, description="Email", title="Email") + uri: Optional[str] = Field(None, title="URI") + + +class VersionItem(SchemaBaseModel): + version: str = Field(..., description="Version number e.g. v1.0", title="Version") + date: str = Field(..., title="Version Date") + responsibility: Optional[str] = Field( + None, description="Version Responsibility Statement", title="Version Responsibility Statement" + ) + notes: Optional[str] = Field(None, title="Version Notes") + + +class UpdateScheduleItem(SchemaBaseModel): + update: Optional[str] = Field(None, title="Schedule date") + + +class TimeCoverageItem(SchemaBaseModel): + start: Optional[str] = Field( + None, description="Time coverage, start date (oldest date for which data are available)", title="Start date" + ) + end: Optional[str] = Field( + None, description="Time coverage, end date (most recent date for which data are available)", title="End date" + ) + + +class PeriodicityItem(SchemaBaseModel): + period: Optional[str] = Field(None, title="Period") + + +class Theme(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Name") + parent_id: Optional[str] = Field(None, title="Parent Identifier") + vocabulary: Optional[str] = Field(None, description="Name of the controlled vocabulary", title="Vocabulary") + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the theme is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Topic(SchemaBaseModel): + id: str = Field(..., title="Unique Identifier") + name: str = Field(..., title="Topic") + parent_id: Optional[str] = Field( + None, description="For subtopics, provide the ID of the parent topic", title="Parent topic Identifier" + ) + vocabulary: Optional[str] = Field( + None, description="Name of the controlled vocabulary, if the topic is from a taxonomy.", title="Vocabulary" + ) + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the topic is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Keyword(SchemaBaseModel): + name: str = Field(..., title="Keyword") + vocabulary: Optional[str] = Field(None, title="Vocabulary") + uri: Optional[str] = Field(None, title="URI") + + +class RefCountryItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Country name") + code: Optional[str] = Field(None, title="Country code") + + +class GeographicUnit(SchemaBaseModel): + name: str = Field( + ..., description="Name of the geographic unit e.g. 'World', 'Africa', 'Afghanistan'", title="Location name" + ) + code: Optional[str] = Field( + None, description="Code of the geographic unit (for countries, preferred = ISO3 code)", title="Location code" + ) + type: Optional[str] = Field( + None, description="Type of geographic unit e.g. country, state, region, province etc", title="Type" + ) + + +class BboxItem(SchemaBaseModel): + west: Optional[str] = Field(None, title="West") + east: Optional[str] = Field(None, title="East") + south: Optional[str] = Field(None, title="South") + north: Optional[str] = Field(None, title="North") + + +class Sponsor(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name of the sponsoring agency", title="Funding Agency/Sponsor") + abbreviation: Optional[str] = Field( + None, description="Abbreviation (acronym) of the sponsoring agency", title="Abbreviation" + ) + role: Optional[str] = Field(None, description="Specific role of the sponsoring agency", title="Role") + grant: Optional[str] = Field(None, description="Grant number", title="Grant") + uri: Optional[str] = Field(None, title="URI") + + +class Acknowledgment(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + uri: Optional[str] = Field(None, title="URI") + + +class Contact(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + role: Optional[str] = Field(None, title="Role") + affiliation: Optional[str] = Field(None, title="Affiliation") + email: Optional[str] = Field(None, title="Email") + telephone: Optional[str] = Field(None, title="Telephone") + uri: Optional[str] = Field(None, title="URI") + + +class Link(SchemaBaseModel): + uri: Optional[str] = Field(None, title="URI") + description: Optional[str] = Field(None, title="Description") + + +class Language(SchemaBaseModel): + name: Optional[str] = Field(None, description="Language title", title="Name") + code: Optional[str] = Field(None, title="code") + + +class AccessOption(SchemaBaseModel): + type: str = Field(..., description="Access type e.g. API, Bulk, Query, etc", title="Access type") + uri: Optional[str] = Field(None, title="URI") + note: Optional[str] = Field(None, description="Note", title="Note") + + +class LicenseItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + uri: Optional[str] = Field(None, title="URI") + note: Optional[str] = Field(None, title="Note") + + +class Note(SchemaBaseModel): + note: Optional[str] = Field(None, title="Note") + + +class DatabaseDescription(SchemaBaseModel): + """ + Database Description + """ + + class Config: + extra = Extra.forbid + + title_statement: TitleStatement = Field(..., description="Study title") + authoring_entity: Optional[List[AuthoringEntityItem]] = Field( + None, + description=( + "The person, corporate body, or agency responsible for the work's substantive and intellectual content." + " Repeat the element for each author, and use 'affiliation' attribute if available. Invert first and last" + " name and use commas." + ), + title="Authoring entity", + ) + abstract: Optional[str] = Field(None, description="A brief description of the database", title="Abstract") + url: Optional[str] = Field(None, description="Link to the dataset web page", title="Dataset URL") + type: Optional[str] = Field(None, description="Dataset type", title="Dataset type") + doi: Optional[str] = Field(None, description="DOI handle", title="DOI") + date_created: Optional[str] = Field( + None, description="Date this version of the dataset was created", title="Date of creation" + ) + date_published: Optional[str] = Field( + None, description="Date this version of the dataset was published", title="Dataset published date" + ) + version: Optional[List[VersionItem]] = Field(None, title="Version Statement") + update_frequency: Optional[str] = Field( + None, + description="Dataset frequency of updates (for datasets updated at regular intervals)", + title="Frequency of update", + ) + update_schedule: Optional[List[UpdateScheduleItem]] = Field( + None, description="Dataset schedule of updates", title="Schedule of updates" + ) + time_coverage: Optional[List[TimeCoverageItem]] = Field( + None, + description=( + "Time coverage for the whole database. This will typically be the min and max dates for which data are" + " available in any series contained in the database." + ), + title="Range of dates covered by the dataset", + ) + time_coverage_note: Optional[str] = Field(None, description="Time coverage note", title="Time coverage note") + periodicity: Optional[List[PeriodicityItem]] = Field( + None, + description=( + "Periodicity of the data contained in the database (NOT the periodicity of update of the database). This" + " describes the various reference periods for the series. Example: `annual`, `quarterly`, `monthly`," + " `daily`." + ), + title="Periodicity of series", + ) + themes: Optional[List[Theme]] = Field(None, description="Themes") + topics: Optional[List[Topic]] = Field( + None, + description="Topics covered by the database (ideally, the list of topics will be a controlled vocabulary)", + title="Topics", + ) + keywords: Optional[List[Keyword]] = Field(None, description="Keywords") + ref_country: Optional[List[RefCountryItem]] = Field( + None, description="List of countries for which data are available", title="Reference country" + ) + geographic_units: Optional[List[GeographicUnit]] = Field( + None, + description=( + "List of geographic units (regions, countries, states, provinces, etc.) for which data are available in the" + " database." + ), + title="Geographic locations", + ) + geographic_coverage_note: Optional[str] = Field( + None, description="Notes on geographic coverage", title="Geographic coverage notes" + ) + bbox: Optional[List[BboxItem]] = Field(None, description="Geographic bounding box", title="Geographic bounding box") + geographic_granularity: Optional[str] = Field( + None, + description="Granularity of geographic coverage e.g. `national`, `regional`, `provincial`", + title="Geographic granularity", + ) + geographic_area_count: Optional[str] = Field(None, description="Number of geographic areas") + sponsors: Optional[List[Sponsor]] = Field( + None, + description=( + "The source(s) of funds for production of the work. If different funding agencies sponsored different" + " stages of the production process, use the 'role' attribute to distinguish them." + ), + title="Sponsor/Funding Agency", + ) + acknowledgments: Optional[List[Acknowledgment]] = Field( + None, description="Other Acknowledgments", title="Other Acknowledgments" + ) + acknowledgment_statement: Optional[str] = Field( + None, + title=( + "An overall statement of acknowledgment, which can be used as an alternative (or supplement) to the" + " itemized list provided in `acknowledgments`." + ), + ) + contacts: Optional[List[Contact]] = Field(None, description="Contacts", title="Contacts") + links: Optional[List[Link]] = Field(None, description="Related links", title="Related links") + languages: Optional[List[Language]] = Field(None, description="Supported languages") + access_options: Optional[List[AccessOption]] = Field( + None, description="Access options e.g. API, Bulk, Query", title="Access options" + ) + license: Optional[List[LicenseItem]] = Field(None, description="License information", title="License") + citation: Optional[str] = Field(None, title="Citation") + notes: Optional[List[Note]] = Field(None, description="Notes", title="Notes") + disclaimer: Optional[str] = Field(None, title="Disclaimer") + copyright: Optional[str] = Field(None, title="Copyright") + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class TimeseriesDatabaseSchema(SchemaBaseModel): + """ + Schema for timeseries database + """ + + published: Optional[int] = Field(0, description="0=draft, 1=published", title="Status") + overwrite: Optional[Overwrite] = Field("no", description="Overwrite database if already exists?") + metadata_information: Optional[MetadataInformation] = Field( + None, description="Document description", title="Document metadata information" + ) + database_description: DatabaseDescription = Field( + ..., description="Database Description", title="Database Description" + ) + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + additional: Optional[Dict[str, Any]] = Field( + None, description="Any other custom metadata not covered by the schema", title="Additional custom metadata" + ) diff --git a/pydantic_schemas/metadata_manager.py b/pydantic_schemas/metadata_manager.py new file mode 100644 index 0000000..70f9c69 --- /dev/null +++ b/pydantic_schemas/metadata_manager.py @@ -0,0 +1,279 @@ +from copy import copy +from typing import Dict, List, Optional, Type, Union + +from openpyxl import load_workbook +from pydantic import BaseModel + +from . import ( # image_schema, + document_schema, + geospatial_schema, + indicator_schema, + indicators_db_schema, + microdata_schema, + resource_schema, + script_schema, + table_schema, + video_schema, +) +from .utils.excel_to_pydantic import excel_doc_to_pydantic, excel_single_sheet_to_pydantic +from .utils.pydantic_to_excel import write_across_many_sheets, write_to_single_sheet +from .utils.quick_start import make_skeleton +from .utils.utils import merge_dicts, standardize_keys_in_dict + + +class MetadataManager: + """ + Interface with Excel for creating, saving and updating metadata for various types: + document, indicator, indicators_db, microdata, resource, script, table, video + + Retrieve pydantic model definitions for each metadata type + """ + + _TYPE_TO_SCHEMA = { + "document": document_schema.ScriptSchemaDraft, + "geospatial": geospatial_schema.GeospatialSchema, + # "image":image_schema.ImageDataTypeSchema, + "resource": resource_schema.Model, + "script": script_schema.ResearchProjectSchemaDraft, + "microdata": microdata_schema.MicrodataSchema, + "table": table_schema.Model, + "indicator": indicator_schema.TimeseriesSchema, + "indicators_db": indicators_db_schema.TimeseriesDatabaseSchema, + "video": video_schema.Model, + } + + _TYPE_TO_WRITER = { + "document": write_across_many_sheets, + # "geospatial":, + # "image":, + "resource": write_to_single_sheet, + "script": write_across_many_sheets, + "microdata": write_across_many_sheets, + "table": write_across_many_sheets, + "indicator": write_across_many_sheets, + "indicators_db": write_to_single_sheet, # one sheet + "video": write_to_single_sheet, # one sheet + } + + _TYPE_TO_READER = { + "document": excel_doc_to_pydantic, + # "geospatial":, + # "image":, + "resource": excel_single_sheet_to_pydantic, + "script": excel_doc_to_pydantic, + "microdata": excel_doc_to_pydantic, + "table": excel_doc_to_pydantic, + "indicator": excel_doc_to_pydantic, + "indicators_db": excel_single_sheet_to_pydantic, # one sheet + "video": excel_single_sheet_to_pydantic, # one sheet + } + + def metadata_class_from_name(self, metadata_name: str) -> Type[BaseModel]: + metadata_name = self.standardize_metadata_name(metadata_name) + schema = self._TYPE_TO_SCHEMA[metadata_name] + return copy(schema) + + @property + def metadata_type_names(self) -> List[str]: + return list(self._TYPE_TO_SCHEMA.keys()) + + def standardize_metadata_name(self, metadata_name: str) -> str: + metadata_name = metadata_name.lower() + metadata_name = metadata_name.replace("-", "_").replace(" ", "_") + if metadata_name == "survey" or metadata_name == "survey_microdata": + metadata_name = "microdata" + elif metadata_name == "timeseries": + metadata_name = "indicator" + elif metadata_name == "timeseries_db": + metadata_name = "indicators_db" + self._raise_if_unsupported_metadata_name(metadata_name=metadata_name) + return metadata_name + + def create_metadata_outline( + self, metadata_name_or_class: Union[str, Type[BaseModel]], debug: bool = False + ) -> BaseModel: + if isinstance(metadata_name_or_class, str): + schema = self.metadata_class_from_name(metadata_name_or_class) + else: + schema = metadata_name_or_class + skeleton_object = make_skeleton(schema, debug=debug) + return skeleton_object + + def write_metadata_outline_to_excel( + self, + metadata_name_or_class: Union[str, Type[BaseModel]], + filename: Optional[str] = None, + title: Optional[str] = None, + ) -> str: + """ + Create an Excel file formatted for writing the given metadata_name metadata. + + Args: + metadata_name_or_class (str or type[BaseModel]): the name of a supported metadata type, currently: + document, indicator, indicators_db, microdata, resource, script, table, video + Currently not supported: + geospatial, image + If passed as a BaseModel type, for instance this is what you would do with a template, then the writer + defaults to a single page. + filename (Optional[str]): The path to the Excel file. If None, defaults to {metadata_name}_metadata.xlsx + title (Optional[str]): The title for the Excel sheet. If None, defaults to '{metadata_name} Metadata' + + Returns: + str: filename of metadata file + + Outputs: + An Excel file into which metadata can be entered + """ + if isinstance(metadata_name_or_class, str): + metadata_name = self.standardize_metadata_name(metadata_name_or_class) + if metadata_name == "geospatial": + raise NotImplementedError("Geospatial schema contains an infinite loop so cannot be written to excel") + skeleton_object = self.create_metadata_outline(metadata_name, debug=False) + writer = self._TYPE_TO_WRITER[metadata_name] + if filename is None: + filename = f"{metadata_name}_metadata.xlsx" + if title is None: + title = f"{metadata_name.capitalize()} Metadata" + else: + skeleton_object = make_skeleton(metadata_name_or_class, debug=False) + writer = write_to_single_sheet + metadata_name = metadata_name_or_class.model_json_schema()["title"] + if filename is None: + filename = f"{metadata_name}_metadata.xlsx" + if title is None: + title = f"{metadata_name.capitalize()} Metadata" + + if not str(filename).endswith(".xlsx"): + filename += ".xlsx" + writer(filename, skeleton_object, metadata_name, title) + return filename + + def save_metadata_to_excel( + self, + metadata_name_or_class: Union[str, Type[BaseModel]], + object: BaseModel, + filename: Optional[str] = None, + title: Optional[str] = None, + ) -> str: + """ + Save an Excel document of the given metadata object. + + Args: + metadata_name_or_class (str or type[BaseModel]): the name of a supported metadata type, currently: + document, indicator, indicators_db, microdata, resource, script, table, video + Currently not supported: + geospatial, image + If passed as a BaseModel type, for instance this is what you would do with a template, then the writer defaults to a single page. + object (BaseModel): The pydantic object to save to the Excel file. + filename (Optional[str]): The path to the Excel file. Defaults to {name}_metadata.xlsx + title (Optional[str]): The title for the Excel sheet. Defaults to '{name} Metadata' + + Returns: + str: filename of metadata file + + Outputs: + An Excel file containing the metadata from the pydantic object. This file can be updated as needed. + """ + if isinstance(metadata_name_or_class, str): + metadata_name = self.standardize_metadata_name(metadata_name_or_class) + if metadata_name == "geospatial": + raise NotImplementedError("Geospatial schema contains an infinite loop so cannot be written to excel") + schema = self.metadata_class_from_name(metadata_name) + writer = self._TYPE_TO_WRITER[metadata_name] + else: + metadata_name = metadata_name_or_class.model_json_schema()["title"] + schema = metadata_name_or_class + writer = write_to_single_sheet + skeleton_object = self.create_metadata_outline(metadata_name_or_class=metadata_name_or_class, debug=False) + + if filename is None: + filename = f"{metadata_name}_metadata.xlsx" + if not str(filename).endswith(".xlsx"): + filename += ".xlsx" + if title is None: + title = f"{metadata_name.capitalize()} Metadata" + + combined_dict = merge_dicts( + skeleton_object.model_dump(), + object.model_dump(exclude_none=True, exclude_unset=True, exclude_defaults=True), + ) + combined_dict = standardize_keys_in_dict(combined_dict) + new_ob = schema(**combined_dict) + + # writer = self._TYPE_TO_WRITER[metadata_name] + writer(filename, new_ob, metadata_name, title) + return filename + + @staticmethod + def _get_metadata_name_from_excel_file(filename: str) -> str: + error_message = "Improperly formatted Excel file for metadata" + workbook = load_workbook(filename) + # Select the 'metadata' sheet + try: + sheet = workbook["metadata"] + # Get the value of cell C1 + type_info = sheet["C1"].value + except KeyError: + raise ValueError(f"Sheet 'metadata' not found. {error_message}") + except Exception as e: + raise ValueError(f"Error reading Excel file: {e}") + finally: + # Close the workbook + workbook.close() + + if not type_info or not isinstance(type_info, str): + raise ValueError(f"Cell C3 is empty or not a string. {error_message}") + + cell_values = type_info.split(" ") + + if len(cell_values) < 3 or cell_values[1] != "type" or cell_values[2] != "metadata": + raise ValueError(f"Cell C3 is improperly formatted. {error_message}") + + return cell_values[0] + + def read_metadata_from_excel(self, filename: str, metadata_class: Optional[Type[BaseModel]] = None) -> BaseModel: + """ + Read in metadata from an appropriately formatted Excel file as a pydantic object. + If using standard metadata types (document, indicator, indicators_db, microdata, resource, script, table, video) then there is no need to pass in the metadata_class. But if using a template, then the class must be provided. + + Args: + filename (str): The path to the Excel file. + metadata_class (Optional type of BaseModel): A pydantic class type correspondong to the type used to write the Excel file + + Returns: + BaseModel: a pydantic object containing the metadata from the file + """ + metadata_name = self._get_metadata_name_from_excel_file(filename) + try: + metadata_name = self.standardize_metadata_name(metadata_name) + schema = self._TYPE_TO_SCHEMA[metadata_name] + reader = self._TYPE_TO_READER[metadata_name] + except ValueError as e: + if metadata_class is None: + raise ValueError( + f"'{metadata_name}' not supported. Must be: {list(self._TYPE_TO_SCHEMA.keys())} or try passing in the metadata_class" + ) from e + schema = metadata_class + reader = excel_single_sheet_to_pydantic + read_object = reader(filename, schema) + + skeleton_object = self.create_metadata_outline(metadata_name_or_class=schema, debug=False) + + read_object_dict = read_object.model_dump(exclude_none=True, exclude_unset=True, exclude_defaults=True) + combined_dict = merge_dicts( + skeleton_object.model_dump(), + read_object_dict, + ) + combined_dict = standardize_keys_in_dict(combined_dict) + new_ob = schema(**combined_dict) + return new_ob + + def _raise_if_unsupported_metadata_name(self, metadata_name: str): + """ + If the type is specifically unsupported - geospatial or image - a NotImplementedError is raised + If the type is simply unknown then a ValueError is raised. + """ + if metadata_name == "image": + raise NotImplementedError("Due to an issue with image metadata schema definition causing __root__ errors") + if metadata_name not in self._TYPE_TO_SCHEMA.keys(): + raise ValueError(f"'{metadata_name}' not supported. Must be: {list(self._TYPE_TO_SCHEMA.keys())}") diff --git a/pydantic_schemas/microdata_schema.py b/pydantic_schemas/microdata_schema.py new file mode 100644 index 0000000..2c94423 --- /dev/null +++ b/pydantic_schemas/microdata_schema.py @@ -0,0 +1,1446 @@ +# generated by datamodel-codegen: +# filename: microdata-schema.json +# timestamp: 2024-09-13T19:00:25+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import Extra, Field, constr + +from .utils.schema_base_model import SchemaBaseModel + + +class AccessPolicy(Enum): + """ + Data access policy for attached microdata resources + """ + + direct = "direct" + open = "open" + public = "public" + licensed = "licensed" + remote = "remote" + data_na = "data_na" + + +class Overwrite(Enum): + """ + Overwrite survey if already exists? + """ + + yes = "yes" + no = "no" + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class ModelInfoItem(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + author: Optional[str] = Field(None, title="Author") + version: Optional[str] = Field(None, title="Version") + model_id: Optional[str] = Field(None, title="Model Identifier") + nb_topics: Optional[float] = Field(None, title="Number of topics") + description: Optional[str] = Field(None, title="Description") + corpus: Optional[str] = Field(None, title="Corpus name") + uri: Optional[str] = Field(None, title="URI") + + +class TopicWord(SchemaBaseModel): + word: Optional[str] = Field(None, title="Word") + word_weight: Optional[float] = Field(None, title="Word weight") + + +class TopicDescriptionItem(SchemaBaseModel): + topic_id: Optional[Union[int, str]] = Field(None, title="Topic identifier") + topic_score: Optional[Union[float, str]] = Field(None, title="Topic score") + topic_label: Optional[str] = Field(None, title="Topic label") + topic_words: Optional[List[TopicWord]] = Field(None, description="Words", title="Topic words") + + +class LdaTopic(SchemaBaseModel): + class Config: + extra = Extra.forbid + + model_info: Optional[List[ModelInfoItem]] = Field(None, title="Model information") + topic_description: Optional[List[TopicDescriptionItem]] = Field(None, title="Topic information") + + +class Embedding(SchemaBaseModel): + id: str = Field(..., title="Vector Model ID") + description: Optional[str] = Field(None, title="Vector Model Description") + date: Optional[str] = Field(None, title="Date (YYYY-MM-DD)") + vector: Dict[str, Any] = Field(..., title="Vector") + + +class DatafileSchema(SchemaBaseModel): + file_id: str = Field(..., title="File unique ID") + file_name: str = Field(..., title="File name") + file_type: Optional[str] = Field( + None, + description=( + "Types of data files include raw data (ASCII, EBCDIC, etc.) and software-dependent files such as SAS" + " datasets, SPSS export files, etc." + ), + title="File type", + ) + description: Optional[str] = Field(None, title="File description") + case_count: Optional[int] = Field(None, title="Cases count") + var_count: Optional[int] = Field(None, title="Variable count") + producer: Optional[str] = Field(None, description="File producer") + data_checks: Optional[str] = Field(None, title="Processing checks") + missing_data: Optional[str] = Field(None, title="Missing data") + version: Optional[str] = Field(None, title="Version") + notes: Optional[str] = Field(None, title="File notes") + + +class VarIntrvl(Enum): + """ + indicates the interval type; options are discrete or continuous. + """ + + discrete = "discrete" + contin = "contin" + + +class VarSumstatItem(SchemaBaseModel): + type: Optional[str] = Field(None, title="Type") + value: Optional[Union[int, str]] = Field(None, title="Value") + wgtd: Optional[str] = Field(None, description="For weighted values, enter `weighted`", title="Weighted") + + +class Stat(SchemaBaseModel): + type: Optional[str] = Field(None, description="Type such as frequency, percent, etc", title="Type") + value: Optional[Union[int, str]] = Field(None, title="Value") + wgtd: Optional[str] = Field(None, description="For weighted values, enter `weighted`", title="Weighted") + + +class VarCatgryItem(SchemaBaseModel): + value: Optional[str] = Field(None, title="Value") + label: Optional[str] = Field(None, title="Label") + stats: Optional[List[Stat]] = Field( + None, description="Category level statistics e.g. frequency", title="Category statistics" + ) + + +class VarStdCatgry(SchemaBaseModel): + """ + Standard category codes used in the variable, like industry codes, employment codes, or social class codes. The attribute `date` is provided to indicate the version of the code in place at the time of the study. The attribute `URI` is provided to indicate a URN or URL that can be used to obtain an electronic list of the category codes. + """ + + name: Optional[str] = Field(None, title="Standard classification name") + source: Optional[str] = Field(None, title="Source") + date: Optional[str] = Field(None, title="Date") + uri: Optional[str] = Field(None, title="URI") + + +class VarConceptItem(SchemaBaseModel): + title: str = Field(..., title="Concept") + vocab: Optional[str] = Field(None, title="Vocabulary") + uri: Optional[str] = Field(None, title="Vocabulary URI") + + +class VarFormat(SchemaBaseModel): + """ + The technical format of the variable in question. Attributes for this element include: 'type', which indicates if the variable is character or numeric; 'formatname,' which in some cases may provide the name of the particular, proprietary format actually used; 'schema,' which identifies the vendor or standards body that defined the format (acceptable choices are SAS, SPSS, IBM, ANSI, ISO, XML-data or other); 'category,' which describes what kind of data the format represents, and includes date, time, currency, or 'other' conceptual possibilities. + """ + + type: Optional[str] = Field(None, title="Type") + name: Optional[str] = Field(None, title="Name") + note: Optional[str] = Field(None, title="Note") + + +class VariableSchema(SchemaBaseModel): + file_id: str = Field(..., description="File to which the variable belongs", title="File ID e.g. F1") + vid: str = Field(..., description="Unique variable ID e.g. V1, V2", title="Variable unique ID") + name: str = Field(..., title="Variable name") + labl: str = Field(..., title="Variable label") + var_intrvl: Optional[VarIntrvl] = Field( + None, description="indicates the interval type; options are discrete or continuous.", title="Interval type" + ) + var_dcml: Optional[str] = Field( + None, description="Number of decimal points in the variable", title="Variable decimal points" + ) + var_wgt: Optional[int] = Field(0, description="indicates whether the variable is a weight", title="Weight") + loc_start_pos: Optional[int] = Field(None, title="Variable start position") + loc_end_pos: Optional[int] = Field(None, title="Variable end position") + loc_width: Optional[int] = Field(None, title="Variable width") + loc_rec_seg_no: Optional[int] = Field( + None, title="Record segment number, deck or card number the variable is located on" + ) + var_imputation: Optional[str] = Field( + None, + description=( + "According to the Statistical Terminology glossary maintained by the National Science Foundation, this is" + " `the process by which one estimates missing values for items that a survey respondent failed to provide,`" + " and if applicable in this context, it refers to the type of procedure used. " + ), + title="Imputation", + ) + var_derivation: Optional[str] = Field( + None, + description=( + "Used only in the case of a derived variable, this element provides both a description of how the" + " derivation was performed and the command used to generate the derived variable, as well as a" + " specification of the other variables in the study used to generate the derivation. The `var` attribute" + " provides the ID values of the other variables in the study used to generate this derived variable." + ), + title="Derivation", + ) + var_security: Optional[str] = Field(None, title="Security") + var_respunit: Optional[str] = Field(None, title="Source of information") + var_qstn_preqtxt: Optional[str] = Field( + None, + description="Text describing a set of conditions under which a question might be asked.", + title="Pre-question text", + ) + var_qstn_qstnlit: Optional[str] = Field(None, title="Literal question") + var_qstn_postqtxt: Optional[str] = Field(None, title="Post-question text") + var_forward: Optional[str] = Field( + None, + description=( + "Contains a reference to IDs of possible following questions. This can be used to document forward skip" + " instructions." + ), + title="Forward skip", + ) + var_backward: Optional[str] = Field( + None, + description=( + "Contains a reference to IDs of possible preceding questions. This can be used to document backward skip" + " instructions." + ), + title="Backward skip", + ) + var_qstn_ivulnstr: Optional[str] = Field(None, title="Interviewer instructions") + var_universe: Optional[str] = Field(None, title="Universe") + var_sumstat: Optional[List[VarSumstatItem]] = Field( + None, + description=( + "One or more statistical measures that describe the responses to a particular variable and may include one" + " or more standard summaries, e.g., minimum and maximum values, median, mode, etc. \n\n The attribute" + " 'type' denotes the type of statistics being shown: mean, median, mode, valid cases, invalid cases," + " minimum, maximum, or standard deviation." + ), + ) + var_txt: Optional[str] = Field(None, title="Variable description") + var_catgry: Optional[List[VarCatgryItem]] = Field(None, title="Categories") + var_std_catgry: Optional[VarStdCatgry] = Field( + None, + description=( + "Standard category codes used in the variable, like industry codes, employment codes, or social class" + " codes. The attribute `date` is provided to indicate the version of the code in place at the time of the" + " study. The attribute `URI` is provided to indicate a URN or URL that can be used to obtain an electronic" + " list of the category codes." + ), + title="Standard categories", + ) + var_codinstr: Optional[str] = Field(None, title="Recoding and derivation") + var_concept: Optional[List[VarConceptItem]] = Field(None, title="Concepts") + var_format: Optional[VarFormat] = Field( + None, + description=( + "The technical format of the variable in question. Attributes for this element include: 'type', which" + " indicates if the variable is character or numeric; 'formatname,' which in some cases may provide the name" + " of the particular, proprietary format actually used; 'schema,' which identifies the vendor or standards" + " body that defined the format (acceptable choices are SAS, SPSS, IBM, ANSI, ISO, XML-data or other);" + " 'category,' which describes what kind of data the format represents, and includes date, time, currency," + " or 'other' conceptual possibilities." + ), + title="Variable format", + ) + var_notes: Optional[str] = Field(None, title="Variable notes") + + +class GroupType(Enum): + subject = "subject" + section = "section" + multiResp = "multiResp" + grid = "grid" + display = "display" + repetition = "repetition" + version = "version" + iteration = "iteration" + analysis = "analysis" + pragmatic = "pragmatic" + record = "record" + file = "file" + randomized = "randomized" + other = "other" + + +class VariableGroupSchema(SchemaBaseModel): + vgid: constr(max_length=45) = Field( + ..., description="Unique ID for the variable group e.g. VG1", title="Variable Group ID" + ) + variables: Optional[constr(max_length=5000)] = Field( + None, description="List of variables for the group seperated by space e.g. V1 V2 V3", title="Variables" + ) + variable_groups: Optional[constr(max_length=1000)] = Field( + None, description="List of sub-groups e.g. VG2 VG3 VG4", title="Variable groups" + ) + group_type: Optional[GroupType] = Field(None, title="Type") + label: Optional[constr(max_length=255)] = Field(None, title="Label") + universe: Optional[constr(max_length=255)] = Field(None, title="Universe") + notes: Optional[constr(max_length=500)] = Field(None, title="Notes") + txt: Optional[constr(max_length=500)] = Field(None, title="Text") + definition: Optional[constr(max_length=500)] = Field(None, title="Definition") + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class VersionStatement(SchemaBaseModel): + """ + Version Statement + """ + + version: Optional[str] = Field(None, title="Version") + version_date: Optional[str] = Field(None, title="Version Date") + version_resp: Optional[str] = Field( + None, + description="The organization or person responsible for the version of the work", + title="Version Responsibility Statement", + ) + version_notes: Optional[str] = Field(None, title="Version Notes") + + +class DocDesc(SchemaBaseModel): + """ + Document Description + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + prod_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version_statement: Optional[VersionStatement] = Field( + None, description="Version Statement", title="Version Statement" + ) + + +class Identifier(SchemaBaseModel): + type: Optional[str] = Field( + None, description="Type of identifier e.g. `doi`, `handle`, `other`", title="Identifier type" + ) + identifier: str = Field(..., title="Identifier") + + +class TitleStatement(SchemaBaseModel): + """ + Study title + """ + + idno: str = Field( + ..., + description=( + "The ID number of a dataset is a unique number that is used to identify a particular survey. Define and use" + " a consistent scheme to use. Such an ID could be constructed as follows:" + " country-producer-survey-year-version where \n - country is the 3-letter ISO country abbreviation \n -" + " producer is the abbreviation of the producing agency \n - survey is the survey abbreviation \n - year is" + " the reference year (or the year the survey started) \n - version is the number dataset version number" + " (see Version Description below)" + ), + title="Unique user defined ID", + ) + identifiers: Optional[List[Identifier]] = Field(None, description="Other identifiers", title="Other identifiers") + title: str = Field( + ..., + description=( + "The title is the official name of the survey as it is stated on the questionnaire or as it appears in the" + " design documents. The following items should be noted:\n - Include the reference year(s) of the survey in" + " the title. \n - Do not include the abbreviation of the survey name in the title. \n - As the survey title" + " is a proper noun, the first letter of each word should be capitalized (except for prepositions or other" + " conjunctions).\n - Including the country name in the title is optional." + ), + title="Survey title", + ) + sub_title: Optional[str] = Field(None, description="A short subtitle for the survey", title="Survey subtitle") + alternate_title: Optional[str] = Field( + None, + description=( + "The abbreviation of a survey is usually the first letter of each word of the titled survey. The survey" + " reference year(s) may be included." + ), + title="Abbreviation or Acronym", + ) + translated_title: Optional[str] = Field( + None, + description="In countries with more than one official language, a translation of the title may be provided.", + title="Translated Title", + ) + + +class AuthoringEntityItem(SchemaBaseModel): + name: str = Field(..., title="Agency Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + + +class OthIdItem(SchemaBaseModel): + name: str = Field(..., description="Person or Agency name", title="Name") + role: Optional[str] = Field(None, title="Role") + affiliation: Optional[str] = Field(None, title="Affiliation") + + +class FundingAgency(SchemaBaseModel): + name: str = Field(..., title="Funding Agency/Sponsor") + abbr: Optional[str] = Field(None, title="Abbreviation") + grant: Optional[str] = Field(None, title="Grant Number") + role: Optional[str] = Field(None, title="Role") + + +class ProductionStatement(SchemaBaseModel): + """ + Production Statement + """ + + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + copyright: Optional[str] = Field(None, title="Copyright") + prod_date: Optional[str] = Field( + None, + description=( + "Date when the marked-up document/marked-up document source/data collection/other material(s) were produced" + " (not distributed or archived). The ISO standard for dates (YYYY-MM-DD) is recommended for use with the" + " date attribute. Production date for data collection (2.1.3.3) maps to Dublin Core Date element." + ), + title="Production Date", + ) + prod_place: Optional[str] = Field( + None, description="Address of the archive or organization that produced the work", title="Production Place" + ) + funding_agencies: Optional[List[FundingAgency]] = Field( + None, + description=( + "The source(s) of funds for production of the work. If different funding agencies sponsored different" + " stages of the production process, use the 'role' attribute to distinguish them." + ), + title="Funding Agency/Sponsor", + ) + + +class Distributor(SchemaBaseModel): + name: str = Field(..., description="Organization name", title="Organization name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + uri: Optional[str] = Field(None, title="URI") + + +class ContactItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + email: Optional[str] = Field(None, title="Email") + uri: Optional[str] = Field(None, title="URI") + + +class DepositorItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + uri: Optional[str] = Field(None, title="URI") + + +class DistributionStatement(SchemaBaseModel): + """ + Distribution Statement + """ + + distributors: Optional[List[Distributor]] = Field( + None, + description=( + "The organization designated by the author or producer to generate copies of the particular work including" + " any necessary editions or revisions. Names and addresses may be specified and other archives may be" + " co-distributors. A URI attribute is included to provide an URN or URL to the ordering service or download" + " facility on a Web site." + ), + title="Distributor", + ) + contact: Optional[List[ContactItem]] = Field(None, description="Contact", title="Contact") + depositor: Optional[List[DepositorItem]] = Field(None, description="Depositor", title="Depositor") + deposit_date: Optional[str] = Field(None, title="Date of Deposit") + distribution_date: Optional[str] = Field(None, title="Date of Distribution") + + +class SeriesStatement(SchemaBaseModel): + """ + Series Statement + """ + + series_name: Optional[str] = Field( + None, description="The name of the series to which the work belongs.", title="Series Name" + ) + series_info: Optional[str] = Field( + None, + description=( + "A survey may be repeated at regular intervals (such as an annual labour force survey), or be part of an" + " international survey program (such as the MICS, CWIQ, DHS, LSMS and others). The Series information is a" + " description of this `collection` of surveys. A brief description of the characteristics of the survey," + " including when it started, how many rounds were already implemented, and who is in charge would be" + " provided here." + ), + title="Series Information", + ) + + +class Holding(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + location: Optional[str] = Field(None, title="Location") + callno: Optional[str] = Field(None, title="Callno") + uri: Optional[str] = Field(None, title="URI") + + +class AgencyItem(SchemaBaseModel): + name: str = Field(..., title="Funding Agency/Sponsor") + affiliation: Optional[str] = Field(None, title="Affiliation") + abbr: Optional[str] = Field(None, title="Abbreviation") + + +class StudyAuthorization(SchemaBaseModel): + """ + Provides structured information on the agency that authorized the study, the date of authorization, and an authorization statement + """ + + date: Optional[str] = Field(None, title="Authorization Date") + agency: Optional[List[AgencyItem]] = Field( + None, + description=( + "The source(s) of funds for production of the work. If different funding agencies sponsored different" + " stages of the production process, use the 'role' attribute to distinguish them." + ), + title="Authorizing Agency", + ) + authorization_statement: Optional[str] = Field( + None, description="Authorization Statement", title="Authorization Statement" + ) + + +class Keyword(SchemaBaseModel): + keyword: Optional[str] = Field(None, title="Keyword") + vocab: Optional[str] = Field(None, title="Vocabulary") + uri: Optional[str] = Field(None, title="uri") + + +class Topic(SchemaBaseModel): + topic: str = Field(..., title="Topic") + vocab: Optional[str] = Field(None, title="Vocab") + uri: Optional[str] = Field(None, title="URI") + + +class TimePeriod(SchemaBaseModel): + start: str = Field(..., description="Start date", title="Start date") + end: Optional[str] = Field(None, description="End date", title="End date") + cycle: Optional[str] = Field(None, description="Cycle", title="Cycle") + + +class CollDate(TimePeriod): + pass + + +class NationItem(SchemaBaseModel): + name: str = Field(..., description="Country name", title="Name") + abbreviation: Optional[str] = Field(None, description="Country ISO code", title="Country code") + + +class BboxItem(SchemaBaseModel): + west: Optional[str] = Field(None, title="West") + east: Optional[str] = Field(None, title="East") + south: Optional[str] = Field(None, title="South") + north: Optional[str] = Field(None, title="North") + + +class BoundPolyItem(SchemaBaseModel): + lat: Optional[str] = Field( + None, + description=( + "Latitude (y coordinate) of a point. Valid range expressed in decimal degrees is as follows: -90,0 to 90,0" + " degrees (latitude)" + ), + title="Latitude", + ) + lon: Optional[str] = Field( + None, + description=( + "Longitude (x coordinate) of a point. Valid range expressed in decimal degrees is as follows: -180,0 to" + " 180,0 degrees (longitude)" + ), + title="longitude", + ) + + +class Standard(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + producer: Optional[str] = Field(None, title="Producer") + + +class QualityStatement(SchemaBaseModel): + """ + The quality statement provides elements to describe compliance with quality standards in the form of a statement and an itemized list of standards complied with, and an element to provide other quality statement. + """ + + compliance_description: Optional[str] = Field(None, title="Standard compliance description") + standards: Optional[List[Standard]] = Field(None, description="Standards", title="Standards") + other_quality_statement: Optional[str] = Field(None, title="Other quality statement") + + +class EvaluatorItem(SchemaBaseModel): + name: str = Field(..., title="Funding Agency/Sponsor") + affiliation: Optional[str] = Field(None, title="Affiliation") + abbr: Optional[str] = Field(None, title="Abbreviation") + role: Optional[str] = Field(None, title="Role") + + +class ExPostEvaluation(SchemaBaseModel): + """ + This structure consists of two parts, standardsCompliance and otherQualityStatements. In standardsCompliance list all specific standards complied with during the execution of this study. Note the standard name and producer and how the study complied with the standard. Enter any additional quality statements in otherQualityStatements. + """ + + completion_date: Optional[str] = Field(None, title="Evaluation completion date") + type: Optional[str] = Field(None, title="Evaluation type") + evaluator: Optional[List[EvaluatorItem]] = Field(None, description="Evaluators", title="Evaluators") + evaluation_process: Optional[str] = Field(None, title="Evaluation process") + outcomes: Optional[str] = Field(None, title="Outcomes") + + +class StudyInfo(SchemaBaseModel): + """ + This section contains information about the data collection's scope across several dimensions, including substantive content, geography, and time. + """ + + study_budget: Optional[str] = Field( + None, + description=( + "Provide a clear summary of the pDescribe the budget of the project in as much detail as needed. Use XHTML" + " structure elements to identify discrete pieces of information in a way that facilitates direct transfer" + " of information on the study budget between DDI 2 and DDI 3 structures.urposes, objectives and content of" + " the survey" + ), + title="Study Budget", + ) + keywords: Optional[List[Keyword]] = Field(None, description="Keywords") + topics: Optional[List[Topic]] = Field(None, description="Topic Classification", title="Topic Classification") + abstract: Optional[str] = Field( + None, + description="Provide a clear summary of the purposes, objectives and content of the survey", + title="Abstract", + ) + time_periods: Optional[List[TimePeriod]] = Field( + None, + description=( + "This field will usually be left empty. Time period differs from the dates of collection as they represent" + " the period for which the data collected are applicable or relevant." + ), + title="Time periods (YYYY/MM/DD)", + ) + coll_dates: Optional[List[CollDate]] = Field( + None, + description=( + "Enter the dates (at least month and year) of the start and end of the data collection. In some cases, data" + " collection for a same survey can be conducted in waves. In such case, you should enter the start and end" + " date of each wave separately, and identify each wave in the 'cycle' field." + ), + title="Dates of Data Collection (YYYY/MM/DD)", + ) + nation: List[NationItem] = Field( + ..., + description=( + "Indicates the country or countries covered in the file. Field `abbreviation` may be used to list common" + " abbreviations; use of ISO country codes is recommended. Maps to Dublin Core Coverage element. Inclusion" + " of this element is recommended." + ), + title="Country", + ) + bbox: Optional[List[BboxItem]] = Field(None, title="Geographic bounding box") + bound_poly: Optional[List[BoundPolyItem]] = Field( + None, + description=( + "This field allows the creation of multiple polygons to describe in a more detailed manner the geographic" + " area covered by the dataset. It should only be used to define the outer boundaries of a covered area. For" + " example, in the United States, such polygons can be created to define boundaries for Hawaii, Alaska, and" + " the continental United States, but not interior boundaries for the contiguous states. This field is used" + " to refine a coordinate-based search, not to actually map an area. \nIf the boundPoly element is used," + " then geoBndBox MUST be present, and all points enclosed by the boundPoly MUST be contained within the" + " geoBndBox. Elements westBL, eastBL, southBL, and northBL of the geoBndBox should each be represented in" + " at least one point of the boundPoly description." + ), + title="Geographic Bounding Polygon", + ) + geog_coverage: Optional[str] = Field( + None, + description=( + " Information on the geographic coverage of the data. Includes the total geographic scope of the data, and" + " any additional levels of geographic coding provided in the variables. Maps to Dublin Core Coverage" + " element. Inclusion of this element in the codebook is recommended." + ), + title="Geographic Coverage", + ) + geog_coverage_notes: Optional[str] = Field( + None, description="Geographic coverage notes", title="Geographic Coverage notes" + ) + geog_unit: Optional[str] = Field( + None, description="Lowest level of geographic aggregation covered by the data", title="Geographic Unit" + ) + analysis_unit: Optional[str] = Field( + None, + description=( + "Basic unit(s) of analysis or observation that the study describes: individuals, families/households," + " groups, facilities, institutions/organizations, administrative units, physical locations, etc." + ), + title="Unit of Analysis", + ) + universe: Optional[str] = Field( + None, + description=( + "We are interested here in the survey universe (not the universe of particular sections of the" + " questionnaires or variables), i.e. in the identification of the population of interest in the survey. The" + " universe will rarely be the entire population of the country. Sample household surveys, for example," + " usually do not cover homeless, nomads, diplomats, community households. Some surveys may cover only the" + " population of a particular age group, or only male (or female), etc." + ), + title="Universe", + ) + data_kind: Optional[str] = Field(None, description="Broad classification of the data", title="Kind of Data") + notes: Optional[str] = Field(None, description="Study notes", title="Study notes") + quality_statement: Optional[QualityStatement] = Field( + None, + description=( + "The quality statement provides elements to describe compliance with quality standards in the form of a" + " statement and an itemized list of standards complied with, and an element to provide other quality" + " statement." + ), + title="Quality Statement", + ) + ex_post_evaluation: Optional[ExPostEvaluation] = Field( + None, + description=( + "This structure consists of two parts, standardsCompliance and otherQualityStatements. In" + " standardsCompliance list all specific standards complied with during the execution of this study. Note" + " the standard name and producer and how the study complied with the standard. Enter any additional quality" + " statements in otherQualityStatements." + ), + title="Ex-Post Evaluation", + ) + + +class Participant(SchemaBaseModel): + name: str = Field(..., title="Participant name") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class Resource(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name of the resource", title="Resource name") + origin: Optional[str] = Field( + None, + description=( + "For historical materials, information about the origin(s) of the sources and the rules followed in" + " establishing the sources should be specified. May not be relevant to survey data. " + ), + title="Origin of resource", + ) + characteristics: Optional[str] = Field( + None, + description="Assessment of characteristics and quality of source material. May not be relevant to survey data.", + title="Characteristics of resource", + ) + + +class DevelopmentActivityItem(SchemaBaseModel): + activity_type: Optional[str] = Field(None, title="Development activity type") + activity_description: Optional[str] = Field(None, title="Development activity description") + participants: Optional[List[Participant]] = Field(None, description="Participants", title="Participants") + resources: Optional[List[Resource]] = Field( + None, description="Development activity resources", title="Development activity resources" + ) + outcome: Optional[str] = Field( + None, description="Development Activity Outcome", title="Development Activity Outcome" + ) + + +class StudyDevelopment(SchemaBaseModel): + """ + Describe the process of study development as a series of development activities. These activities can be typed using a controlled vocabulary. Describe the activity, listing participants with their role and affiliation, resources used (sources of information), and the outcome of the development activity. + """ + + development_activity: Optional[List[DevelopmentActivityItem]] = Field(None, title="Development activity") + + +class DataCollector(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + abbr: Optional[str] = Field(None, title="Abbreviation") + role: Optional[str] = Field(None, title="Role") + + +class CollectorTrainingItem(SchemaBaseModel): + type: Optional[str] = Field( + None, description="The percentage of sample members who provided information", title="Training type" + ) + training: Optional[str] = Field(None, description="Training provided to data collectors", title="Training") + + +class ValidPeriodItem(SchemaBaseModel): + event: Optional[str] = Field(None, description="Event e.g. start, end", title="Event") + date: str = Field(..., description="Date", title="Date") + + +class FrameUnit(SchemaBaseModel): + """ + Provides information about the sampling frame unit. The attribute `isPrimary` is boolean, indicating whether the unit is primary or not. + """ + + is_primary: Optional[Union[bool, str]] = Field(None, description="Is a primary unit?", title="Is Primary") + unit_type: Optional[str] = Field( + None, + description=( + "Describes the type of sampling frame unit. The field `num_of_units` provides the number of units in the" + " sampling frame." + ), + title="Unit Type", + ) + num_of_units: Optional[str] = Field( + None, description="Number of units in the sampling frame", title="Number of units" + ) + + +class ReferencePeriodItem(ValidPeriodItem): + pass + + +class SampleFrame(SchemaBaseModel): + """ + Sample frame describes the sampling frame used for identifying the population from which the sample was taken. For example, a telephone book may be a sample frame for a phone survey. In addition to the name, label and text describing the sample frame, this structure lists who maintains the sample frame, the period for which it is valid, a use statement, the universe covered, the type of unit contained in the frame as well as the number of units available, the reference period of the frame and procedures used to update the frame. + """ + + name: Optional[str] = Field(None, description="Sample frame name", title="Sample frame name") + valid_period: Optional[List[ValidPeriodItem]] = Field( + None, + description="Defines a time period for the validity of the sampling frame. Enter dates in YYYY-MM-DD format.", + title="Valid periods (YYYY/MM/DD)", + ) + custodian: Optional[str] = Field( + None, + description=( + "Custodian identifies the agency or individual who is responsible for creating or maintaining the sample" + " frame." + ), + title="Custodian", + ) + universe: Optional[str] = Field( + None, + description=( + "The group of persons or other elements that are the object of research and to which any analytic results" + " refer." + ), + title="Universe", + ) + frame_unit: Optional[FrameUnit] = Field( + None, + description=( + "Provides information about the sampling frame unit. The attribute `isPrimary` is boolean, indicating" + " whether the unit is primary or not." + ), + title="Frame unit", + ) + reference_period: Optional[List[ReferencePeriodItem]] = Field( + None, + description=( + "Indicates the period of time in which the sampling frame was actually used for the study in question. Use" + " ISO 8601 date/time formats to enter the relevant date(s)." + ), + title="Reference periods (YYYY/MM/DD)", + ) + update_procedure: Optional[str] = Field( + None, + description="Description of how and with what frequency the sample frame is updated.", + title="Update procedure", + ) + + +class Source(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name of the source", title="Source name") + origin: Optional[str] = Field( + None, + description=( + "For historical materials, information about the origin(s) of the sources and the rules followed in" + " establishing the sources should be specified. May not be relevant to survey data. " + ), + title="Origin of Source", + ) + characteristics: Optional[str] = Field( + None, + description="Assessment of characteristics and quality of source material. May not be relevant to survey data.", + title="Characteristics of Source Noted", + ) + + +class DataCollection(SchemaBaseModel): + """ + Information about the methodology employed in a data collection + """ + + time_method: Optional[str] = Field( + None, + description=( + "The time method or time dimension of the data collection. Examples: `panel survey`, `h>cross-section`," + " `trend study`, `time-series`" + ), + title="Time Method", + ) + data_collectors: Optional[List[DataCollector]] = Field( + None, + description=( + "The persons and/or agencies that took charge of the data collection. This element includes 3 fields: Name," + " Abbreviation and the Affiliation. In most cases, we will record here the name of the agency, not the name" + " of interviewers. Only in the case of very small-scale surveys, with a very limited number of" + " interviewers, the name of person will be included as well. The field Affiliation is optional and not" + " relevant in all cases." + ), + title="Data Collectors", + ) + collector_training: Optional[List[CollectorTrainingItem]] = Field( + None, + description=( + "Describes the training provided to data collectors including interviewer training, process testing," + " compliance with standards etc. This is repeatable for language and to capture different aspects of the" + " training process. The type attribute allows specification of the type of training being described." + ), + title="Collector training", + ) + frequency: Optional[str] = Field( + None, + description=( + "For data collected at more than one point in time, the frequency with which the data were collected." + " Examples `monthly`, `quarterly`, `yearly`" + ), + title="Frequency of Data Collection", + ) + sampling_procedure: Optional[str] = Field( + None, + description=( + "The type of sample and sample design used to select the survey respondents to represent the population." + " \nThis field only applies to sample surveys. Information on sampling procedure is crucial (although not" + " applicable for censuses and administrative datasets). Examples `National multistage area probability" + " sample`, `Simple random sample`, `Quota sample`" + ), + title="Sampling Procedure", + ) + sample_frame: Optional[SampleFrame] = Field( + None, + description=( + "Sample frame describes the sampling frame used for identifying the population from which the sample was" + " taken. For example, a telephone book may be a sample frame for a phone survey. In addition to the name," + " label and text describing the sample frame, this structure lists who maintains the sample frame, the" + " period for which it is valid, a use statement, the universe covered, the type of unit contained in the" + " frame as well as the number of units available, the reference period of the frame and procedures used to" + " update the frame." + ), + title="Sample Frame", + ) + sampling_deviation: Optional[str] = Field( + None, + description=( + "This field only applies to sample surveys.\nSometimes the reality of the field requires a deviation from" + " the sampling design (for example due to difficulty to access to zones due to weather problems, political" + " instability, etc). If for any reason, the sample design has deviated, this should be reported here. " + ), + title="Deviations from the Sample Design", + ) + coll_mode: Optional[List[str]] = Field( + None, + description=( + "The mode of data collection is the manner in which the interview was conducted or information was" + " gathered. In most cases, the response will be 'face to face interview'. But for some specific kinds of" + " datasets, such as for example data on rain fall, the response will be different." + ), + title="Mode of data collection", + ) + research_instrument: Optional[str] = Field( + None, + description=( + "The type of data collection instrument used. \n`Structured` indicates an instrument in which all" + " respondents are asked the same questions/tests, possibly with precoded answers. If a small portion of" + " such a questionnaire includes open-ended questions, provide appropriate comments. \n`Semi-structured`" + " indicates that the research instrument contains mainly open-ended questions. \n`Unstructured` indicates" + " that in-depth interviews were conducted." + ), + title="Type of Research Instrument", + ) + instru_development: Optional[str] = Field( + None, + description=( + "Describe any development work on the data collection instrument. Type attribute allows for the optional" + " use of a defined development type with or without use of a controlled vocabulary." + ), + title="Instrument development", + ) + instru_development_type: Optional[str] = Field( + None, description="Instrument development type", title="Instrument development type" + ) + sources: Optional[List[Source]] = Field( + None, + description=( + "Description of sources used for the data collection. The element is nestable so that the sources statement" + " might encompass a series of discrete source statements, each of which could contain the facts about an" + " individual source. This element maps to Dublin Core Source element." + ), + title="Sources", + ) + coll_situation: Optional[str] = Field( + None, + description=( + "Description of noteworthy aspects of the data collection situation. Includes information on factors such" + " as cooperativeness of respondents, duration of interviews, number of call-backs, etc." + ), + title="Characteristics of Data Collection Situation - Notes on data collection", + ) + act_min: Optional[str] = Field( + None, + description=( + "Summary of actions taken to minimize data loss. Includes information on actions such as follow-up visits," + " supervisory checks, historical matching, estimation, etc." + ), + title="Supervision", + ) + control_operations: Optional[str] = Field( + None, + description=( + " Methods to facilitate data control performed by the primary investigator or by the data archive. Specify" + " any special programs used for such operations." + ), + title="Control Operations", + ) + weight: Optional[str] = Field( + None, + description=( + "The use of sampling procedures may make it necessary to apply weights to produce accurate statistical" + " results. Describe here the criteria for using weights in analysis of a collection. If a weighting formula" + " or coefficient was developed, provide this formula, define its elements, and indicate how the formula is" + " applied to data." + ), + title="Weighting", + ) + cleaning_operations: Optional[str] = Field( + None, + description="Methods used to `clean` the data collection, e.g., consistency checking, wildcode checking, etc.", + title="Cleaning Operations", + ) + + +class AnalysisInfo(SchemaBaseModel): + """ + Information about Data Appraisal + """ + + response_rate: Optional[str] = Field( + None, description="The percentage of sample members who provided information", title="Response Rate" + ) + sampling_error_estimates: Optional[str] = Field( + None, + description="Measure of how precisely one can estimate a population value from a given sample", + title="Estimates of Sampling Error", + ) + data_appraisal: Optional[str] = Field( + None, + description=( + "Other issues pertaining to data appraisal. Describe here issues such as response variance, nonresponse" + " rate and testing for bias, interviewer and response bias, confidence levels, question bias, etc." + ), + title="Data Appraisal", + ) + + +class DataProcessingItem(SchemaBaseModel): + type: Optional[str] = Field(None, title="Data processing type") + description: str = Field(..., title="Data processing description") + + +class CodingInstruction(SchemaBaseModel): + related_processes: Optional[str] = Field(None, title="Related processes") + type: Optional[str] = Field(None, title="Coding instructions type") + txt: Optional[str] = Field(None, title="Coding instructions text") + command: Optional[str] = Field( + None, + description=( + "Provide command code for the coding instruction. The formalLanguage attribute identifies the language of" + " the command code." + ), + title="Command", + ) + formal_language: Optional[str] = Field( + None, + description="Identifies the language of the command code. e.g. `SPSS`, `R`, `STATA` ", + title="Identify the language of the command code", + ) + + +class Method(SchemaBaseModel): + """ + Methodology and processing + """ + + data_collection: Optional[DataCollection] = Field( + None, description="Information about the methodology employed in a data collection", title="Data Collection" + ) + method_notes: Optional[str] = Field(None, description="Methodology notes", title="Methodology notes") + analysis_info: Optional[AnalysisInfo] = Field( + None, description="Information about Data Appraisal", title="Data Appraisal" + ) + study_class: Optional[Union[str, List[Any]]] = Field( + None, + description=( + "Generally used to give the data archive's class or study status number, which indicates the processing" + " status of the study. May also be used as a text field to describe processing status. Example: `DDA Class" + " C`, `Study is available from http://example.com` " + ), + title="Class of the Study", + ) + data_processing: Optional[List[DataProcessingItem]] = Field( + None, + description=( + "Describes various data processing procedures not captured elsewhere in the documentation, such as" + " topcoding, recoding, suppression, tabulation, etc. The `type` attribute supports better classification of" + " this activity, including the optional use of a controlled vocabulary" + ), + title="Data Processing", + ) + coding_instructions: Optional[List[CodingInstruction]] = Field( + None, + description=( + "Describe specific coding instructions used in data processing, cleaning, assession, or tabulation." + ), + title="Coding Instructions", + ) + + +class DatasetAvailability(SchemaBaseModel): + """ + Information on availability and storage of the collection + """ + + access_place: Optional[str] = Field( + None, + description=( + "Location where the data collection is currently stored. Use the URL field `access_place_url` to provide a" + " URN or URL for the storage site or the actual address from which the data may be downloaded" + ), + title="Location of Data Collection", + ) + access_place_url: Optional[str] = Field( + None, + description=( + "Location where the data collection is currently stored. Provide a URN or URL for the storage site or the" + " actual address from which the data may be downloaded" + ), + title="URL for Location of Data Collection", + ) + original_archive: Optional[str] = Field( + None, + description="Archive from which the data collection was obtained; the originating archive", + title="Archive where study is originally stored", + ) + status: Optional[str] = Field( + None, + description=( + "Statement of collection availability. An archive may need to indicate that a collection is unavailable" + " because it is embargoed for a period of time, because it has been superseded, because a new edition is" + " imminent, etc." + ), + title="Availability Status", + ) + coll_size: Optional[str] = Field( + None, + description=( + "Summarizes the number of physical files that exist in a collection, recording the number of files that" + " contain data and noting whether the collection contains machine-readable documentation and/or other" + " supplementary files and information such as data dictionaries, data definition statements, or data" + " collection instruments." + ), + title="Extent of Collection", + ) + complete: Optional[str] = Field( + None, + description=( + "This item indicates the relationship of the data collected to the amount of data coded and stored in the" + " data collection. Information as to why certain items of collected information were not included in the" + " data file stored by the archive should be provided" + ), + title="Completeness of Study Stored", + ) + file_quantity: Optional[str] = Field( + None, description="Total number of physical files associated with a collection", title="Number of Files" + ) + notes: Optional[str] = Field(None, description="Notes and comments", title="Notes") + + +class ConfDecItem(SchemaBaseModel): + txt: Optional[str] = Field( + None, description="Confidentiality declaration text", title="Confidentiality declaration text" + ) + required: Optional[str] = Field( + None, + description="Is signing of a confidentiality declaration required", + title="Is signing of a confidentiality declaration required?", + ) + form_url: Optional[str] = Field( + None, + description="Provide a URN or URL for online access to a confidentiality declaration form.", + title="Confidentiality declaration form URL", + ) + form_id: Optional[str] = Field( + None, description="Indicates the number or ID of the form that the user must fill out", title="Form ID" + ) + + +class SpecPermItem(SchemaBaseModel): + txt: Optional[str] = Field( + None, description="Confidentiality declaration text", title="Special permissions description" + ) + required: Optional[str] = Field( + None, + description="Indicate if special permissions are required to access a resource", + title="Indicate if special permissions are required to access a resource", + ) + form_url: Optional[str] = Field(None, description="Link to the form URL", title="Form URL") + form_id: Optional[str] = Field( + None, description="Indicates the number or ID of the form that the user must fill out", title="Form ID" + ) + + +class ContactItem1(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + uri: Optional[str] = Field(None, title="URI") + email: Optional[str] = Field(None, title="Email") + + +class DatasetUse(SchemaBaseModel): + """ + Information on terms of use for the data collection + """ + + conf_dec: Optional[List[ConfDecItem]] = Field( + None, + description=( + " This element is used to determine if signing of a confidentiality declaration is needed to access a" + " resource." + ), + title="Confidentiality Declaration", + ) + spec_perm: Optional[List[SpecPermItem]] = Field( + None, + description="Determine if any special permissions are required to access a resource", + title="Special Permissions", + ) + restrictions: Optional[str] = Field( + None, + description=( + "Any restrictions on access to or use of the collection such as privacy certification or distribution" + " restrictions should be indicated here. These can be restrictions applied by the author, producer, or" + " disseminator of the data collection. If the data are restricted to only a certain class of user, specify" + " which type." + ), + title="Restrictions", + ) + contact: Optional[List[ContactItem1]] = Field(None, description="Contact", title="Contact") + cit_req: Optional[str] = Field( + None, + description=( + "Text of requirement that a data collection should be cited properly in articles or other publications that" + " are based on analysis of the data." + ), + title="Citation requirement", + ) + deposit_req: Optional[str] = Field( + None, + description=( + "Information regarding user responsibility for informing archives of their use of data through providing" + " citations to the published work or providing copies of the manuscripts." + ), + title="Deposit requirement", + ) + conditions: Optional[str] = Field( + None, + description=( + "Indicates any additional information that will assist the user in understanding the access and use" + " conditions of the data collection." + ), + title="Conditions", + ) + disclaimer: Optional[str] = Field( + None, description="Information regarding responsibility for uses of the data collection", title="Disclaimer" + ) + + +class DataAccess(SchemaBaseModel): + """ + Data Access + """ + + dataset_availability: Optional[DatasetAvailability] = Field( + None, description="Information on availability and storage of the collection", title="Data Set Availability" + ) + dataset_use: Optional[DatasetUse] = Field( + None, description=" Information on terms of use for the data collection", title="Data Set Availability" + ) + notes: Optional[str] = Field(None, description="Notes and comments", title="Notes") + + +class StudyDesc(SchemaBaseModel): + """ + Study Description + """ + + class Config: + extra = Extra.forbid + + title_statement: TitleStatement = Field(..., description="Study title") + authoring_entity: Optional[List[AuthoringEntityItem]] = Field( + None, + description=( + "The person, corporate body, or agency responsible for the work's substantive and intellectual content." + " Repeat the element for each author, and use 'affiliation' attribute if available. Invert first and last" + " name and use commas." + ), + title="Authoring entity/Primary investigators", + ) + oth_id: Optional[List[OthIdItem]] = Field( + None, + description="Acknowledge any other people and institutions that have in some form contributed to the survey", + title="Other Identifications/Acknowledgments", + ) + production_statement: Optional[ProductionStatement] = Field( + None, description="Production Statement", title="Production Statement" + ) + distribution_statement: Optional[DistributionStatement] = Field( + None, description="Distribution Statement", title="Distribution Statement" + ) + series_statement: Optional[SeriesStatement] = Field(None, description="Series Statement", title="Series Statement") + version_statement: Optional[VersionStatement] = Field( + None, description="Version Statement", title="Version Statement" + ) + bib_citation: Optional[str] = Field( + None, + description=( + "Complete bibliographic reference containing all of the standard elements of a citation that can be used to" + " cite the work. The `'bib_citation_format'` field is provided to enable specification of the particular" + " citation style used, e.g., APA, MLA, Chicago, etc." + ), + title="Bibliographic Citation", + ) + bib_citation_format: Optional[str] = Field( + None, + description="Specification of the particular citation style used, e.g., `APA`, `MLA`, `Chicago`, etc.", + title="Bibliographic Citation Format", + ) + holdings: Optional[List[Holding]] = Field( + None, + description=( + "Information concerning either the physical or electronic holdings of the cited work. Attributes include:" + " location--The physical location where a copy is held; callno--The call number for a work at the location" + " specified; and URI--A URN or URL for accessing the electronic copy of the cited work." + ), + title="Holdings Information", + ) + study_notes: Optional[str] = Field(None, title="Study notes") + study_authorization: Optional[StudyAuthorization] = Field( + None, + description=( + "Provides structured information on the agency that authorized the study, the date of authorization, and an" + " authorization statement" + ), + title="Study Authorization", + ) + study_info: StudyInfo = Field( + ..., + description=( + "This section contains information about the data collection's scope across several dimensions, including" + " substantive content, geography, and time." + ), + title="Study Scope", + ) + study_development: Optional[StudyDevelopment] = Field( + None, + description=( + "Describe the process of study development as a series of development activities. These activities can be" + " typed using a controlled vocabulary. Describe the activity, listing participants with their role and" + " affiliation, resources used (sources of information), and the outcome of the development activity." + ), + title="Study Development", + ) + method: Optional[Method] = Field(None, description="Methodology and processing", title="Methodology and Processing") + data_access: Optional[DataAccess] = Field(None, description="Data Access") + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class DdiSchema(SchemaBaseModel): + """ + Schema for Microdata data type based on DDI 2.5 + """ + + doc_desc: Optional[DocDesc] = None + study_desc: Optional[StudyDesc] = None + data_files: Optional[List[DatafileSchema]] = Field(None, description="Data files") + variables: Optional[List[VariableSchema]] = Field(None, description="Variables") + variable_groups: Optional[List[VariableGroupSchema]] = Field( + None, description="Variable group", title="Variable groups" + ) + + +class MicrodataSchema(DdiSchema): + """ + Schema for Microdata data type based on DDI 2.5 + """ + + repositoryid: Optional[str] = Field( + None, + description="Abbreviation for the collection that owns this survey.", + title="Collection ID that owns the survey", + ) + access_policy: Optional[AccessPolicy] = Field( + "data_na", description="Data access policy for attached microdata resources", title="Data access policy" + ) + published: Optional[int] = Field(0, description="Status of the survey - 0=draft, 1=published") + overwrite: Optional[Overwrite] = Field("no", description="Overwrite survey if already exists?") + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags (user-defined)") + lda_topics: Optional[List[LdaTopic]] = Field(None, description="LDA topics", title="LDA topics") + embeddings: Optional[List[Embedding]] = Field(None, description="Word embeddings", title="Word embeddings") + additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata not covered by DDI elements") diff --git a/pydantic_schemas/resource_schema.py b/pydantic_schemas/resource_schema.py new file mode 100644 index 0000000..d72de0d --- /dev/null +++ b/pydantic_schemas/resource_schema.py @@ -0,0 +1,64 @@ +# generated by datamodel-codegen: +# filename: resource-schema.json +# timestamp: 2024-09-13T19:00:26+00:00 + +from __future__ import annotations + +from typing import Optional + +from pydantic import Field + +from .utils.schema_base_model import SchemaBaseModel + + +class Model(SchemaBaseModel): + """ + External resource schema + """ + + dctype: Optional[str] = Field( + "doc/oth", + description=( + "Document types for external resource e.g. `doc/adm` \n* `doc/adm` - Document, Administrative [doc/adm] \n*" + " `doc/anl` - Document, Analytical [doc/anl] \n* `doc/oth` - Document, Other [doc/oth] \n* `doc/qst` -" + " Document, Questionnaire [doc/qst] \n* `doc/ref` - Document, Reference [doc/ref] \n* `doc/rep` - Document," + " Report [doc/rep] \n* `doc/tec` - Document, Technical [doc/tec] \n* `aud` - Audio [aud]\n* `dat` -" + " Database [dat]\n* `map` - Map [map]\n* `dat/micro` - Microdata File [dat/micro]\n* `pic` - Photo [pic]\n*" + " `prg` - Program [prg]\n* `tbl` - Table [tbl]\n* `vid` - Video [vid] \n* `web` - Web Site [web]" + ), + title="Resource type", + ) + dcformat: Optional[str] = Field( + None, + description=( + "Document file format e.g. `application/zip` \n* `application/x-compressed` - Compressed, Generic \n*" + " `application/zip` - Compressed, ZIP \n* `application/x-cspro` - Data, CSPro \n* `application/dbase` -" + " Data, dBase \n* `application/msaccess` - Data, Microsoft Access \n* `application/x-sas` - Data, SAS " + " \n* `application/x-spss` - Data, SPSS \n* `application/x-stata` - Data, Stata \n* `text` - Document," + " Generic \n* `text/html` - Document, HTML \n* `application/msexcel` - Document, Microsoft Excel \n*" + " `application/mspowerpoint` - Document, Microsoft PowerPoint \n* `application/msword` - Document," + " Microsoft Word \n* `application/pdf` - Document, PDF \n* `application/postscript` - Document," + " Postscript \n* `text/plain` - Document, Plain \n* `text/wordperfect` - Document, WordPerfect \n*" + " `image/gif` - Image, GIF \n* `image/jpeg` - Image, JPEG \n* `image/png` - Image, PNG \n*" + " `image/tiff` - Image, TIFF" + ), + title="Resource Format", + ) + title: str = Field(..., description="Title") + author: Optional[str] = Field(None, description="Author") + dcdate: Optional[str] = Field(None, description="Date") + country: Optional[str] = Field(None, description="Country") + language: Optional[str] = Field(None, description="Language") + contributor: Optional[str] = Field(None, description="Contributor") + publisher: Optional[str] = Field(None, description="Publisher") + rights: Optional[str] = Field(None, description="Rights") + description: Optional[str] = Field(None, description="Description") + abstract: Optional[str] = Field(None, description="Abstract") + toc: Optional[str] = Field(None, description="TOC") + filename: Optional[str] = Field( + None, + description=( + "Resource file name or URL. For uploading a file, use the field `file` in formData or use the `Upload file`" + " endpoint." + ), + ) diff --git a/pydantic_schemas/script_schema.py b/pydantic_schemas/script_schema.py new file mode 100644 index 0000000..f785ffe --- /dev/null +++ b/pydantic_schemas/script_schema.py @@ -0,0 +1,687 @@ +# generated by datamodel-codegen: +# filename: script-schema.json +# timestamp: 2024-09-13T19:00:27+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import Extra, Field + +from .utils.schema_base_model import SchemaBaseModel + + +class Overwrite(Enum): + """ + Overwrite document if already exists? + """ + + yes = "yes" + no = "no" + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class DocDesc(SchemaBaseModel): + """ + Document description; the Document is the file containing the structured metadata + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field( + None, description="List of producers of the document", title="Producers" + ) + prod_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of production" + ) + version: Optional[str] = Field( + None, description="Identify and describe the current version of the document", title="Document version" + ) + + +class Identifier(SchemaBaseModel): + type: Optional[str] = Field( + None, description="Type of identifier e.g. `doi`, `handle`, `other`", title="Identifier type" + ) + identifier: str = Field(..., title="Identifier") + + +class TitleStatement(SchemaBaseModel): + """ + Project title + """ + + idno: str = Field( + ..., + description=( + "The ID number of a research project is a unique number that is used to identify a particular project." + " Define and use a consistent scheme to use." + ), + title="Unique user defined ID", + ) + identifiers: Optional[List[Identifier]] = Field(None, description="Other identifiers", title="Other identifiers") + title: str = Field( + ..., + description=( + "The title is the name of the project, which may correspond to the title of an academic paper, of a project" + " impact evaluation, etc." + ), + title="Project title", + ) + sub_title: Optional[str] = Field(None, description="A short subtitle for the project", title="Project subtitle") + alternate_title: Optional[str] = Field( + None, + description=( + "The abbreviation of a project is usually the first letter of each word of the project title. The project" + " reference year(s) may be included." + ), + title="Abbreviation or acronym", + ) + translated_title: Optional[str] = Field( + None, + description="In countries with more than one official language, a translation of the title may be provided.", + title="Translated title", + ) + + +class OutputItem(SchemaBaseModel): + type: Optional[str] = Field( + None, + description=( + "Type of outputs of the script/research project. Example: `Working paper`, `On-line interactive data" + " visualization` (ideally, a controlled vocabulary should be used)" + ), + title="Type of output", + ) + title: str = Field(..., description="Title of the output", title="Title") + authors: Optional[str] = Field(None, description="Authors", title="Authors") + description: Optional[str] = Field( + None, + description=( + "Brief description of the output; for articles and working papers, this can include the bibliographic" + " citation." + ), + title="Description", + ) + abstract: Optional[str] = Field(None, description="Abstract (for papers, articles, books)", title="Abstract") + uri: Optional[str] = Field(None, description="On-line location of the output", title="URI") + doi: Optional[str] = Field(None, description="Digital Object Identifier (DOI) of the output", title="DOI") + + +class ApprovalProces(SchemaBaseModel): + approval_phase: Optional[str] = Field(None, title="A name of the approval phase") + approval_authority: Optional[str] = Field(None, title="Approval authority") + submission_date: Optional[str] = Field(None, title="Date submitted") + reviewer: Optional[str] = Field(None, title="Reviewer") + review_status: Optional[str] = Field(None, title="Review status") + approval_date: Optional[str] = Field(None, title="Date of approval") + + +class LanguageItem(SchemaBaseModel): + name: str = Field(..., title="Name") + code: Optional[str] = Field(None, title="Code") + + +class VersionStatement(SchemaBaseModel): + """ + Version statement + """ + + version: Optional[str] = Field(None, title="Version") + version_date: Optional[str] = Field(None, title="Version date") + version_resp: Optional[str] = Field( + None, + description="The organization or person responsible for the version of the work", + title="Version responsibility statement", + ) + version_notes: Optional[str] = Field(None, title="Version notes") + + +class Erratum(SchemaBaseModel): + date: Optional[str] = Field( + None, description="Date when the erratum was reported or published", title="Date of erratum" + ) + description: Optional[str] = Field( + None, + description="A description of the erratum, with information on which data, scripts, or output were impacted", + title="Description of the erratum", + ) + + +class Proces(SchemaBaseModel): + name: Optional[str] = Field(None, description="A short name for the implementation phase", title="Phase name") + date_start: Optional[str] = Field( + None, + description="Start date of the phase period (as a string; recommended ISO format YYY or YYY-MM or YYY-MM-DD)", + title="Phase start date", + ) + date_end: Optional[str] = Field( + None, + description="End date of the phase period (as a string; recommended ISO format YYY or YYY-MM or YYY-MM-DD)", + title="Phase end date", + ) + description: Optional[str] = Field( + None, description="Description of the implementation phase", title="Phase description" + ) + + +class AuthorIdItem(SchemaBaseModel): + type: Optional[str] = Field(None, description="Source of identifier, e.g. ORCID", title="Type") + id: Optional[str] = Field( + None, description="Author's unique identifier for the corresponding source", title="Identifier" + ) + + +class AuthoringEntityItem(SchemaBaseModel): + name: str = Field( + ..., + description=( + "Name of the person, corporate body, or agency responsible for the work's substantive and intellectual" + " content. If a person, invert first and last name and use commas." + ), + title="Author (or primary investigator) name", + ) + role: Optional[str] = Field( + None, + description="Title of the person (if any) responsible for the work's substantive and intellectual content.", + title="Role", + ) + affiliation: Optional[str] = Field(None, title="Affiliation of the author/primary investigator") + abbreviation: Optional[str] = Field(None, description="Abbreviation", title="Abbreviation") + email: Optional[str] = Field(None, description="Email", title="Email") + author_id: Optional[List[AuthorIdItem]] = Field( + None, + description="Unique identifier of an author, which may be provided by services like ORCID or other", + title="Author ID", + ) + + +class Contributor(SchemaBaseModel): + name: str = Field( + ..., + description=( + "Name of the person, corporate body, or agency responsible for the work's substantive and intellectual" + " content. If a person, invert first and last name and use commas." + ), + title="Name", + ) + role: Optional[str] = Field( + None, + description="Title of the person (if any) responsible for the work's substantive and intellectual content.", + title="Role", + ) + affiliation: Optional[str] = Field(None, title="Affiliation") + abbreviation: Optional[str] = Field(None, description="Abbreviation", title="Abbreviation") + email: Optional[str] = Field(None, description="Email", title="Email") + url: Optional[str] = Field(None, description="URL", title="URL") + + +class Sponsor(SchemaBaseModel): + name: Optional[str] = Field(None, title="Funding Agency/Sponsor") + abbr: Optional[str] = Field(None, title="Abbreviation") + role: Optional[str] = Field(None, title="Role") + grant_no: Optional[str] = Field(None, title="Grant number") + + +class Curator(SchemaBaseModel): + name: str = Field( + ..., + description=( + "Name of the person, corporate body, or agency responsible for the project curation. If a person, invert" + " first and last name and use commas." + ), + title="Name", + ) + role: Optional[str] = Field( + None, description="Title of the person (if any) responsible for the project curation.", title="Role" + ) + affiliation: Optional[str] = Field(None, title="Affiliation") + abbreviation: Optional[str] = Field(None, description="Abbreviation", title="Abbreviation") + email: Optional[str] = Field(None, description="Email", title="Email") + url: Optional[str] = Field(None, description="URL", title="URL") + + +class ReviewsComment(SchemaBaseModel): + """ + List and description of reviews and comments received on the project + """ + + comment_date: Optional[str] = Field( + None, description="Date when the comment was provided", title="Date of the comment" + ) + comment_by: Optional[str] = Field( + None, + description="Name and title of the comment provider (individual or organization)", + title="Provider of the comment", + ) + comment_description: Optional[str] = Field( + None, description="A description of the comment", title="Description of the comment" + ) + comment_response: Optional[str] = Field( + None, + description="Response by the primary investigator or research team on the comment", + title="Response on the comment", + ) + + +class Acknowledgement(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class RelatedProject(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + uri: Optional[str] = Field(None, title="URI") + note: Optional[str] = Field(None, title="Note") + + +class GeographicUnit(SchemaBaseModel): + name: str = Field( + ..., description="Name of the geographic unit e.g. 'World', 'Africa', 'Afghanistan'", title="Location name" + ) + code: Optional[str] = Field( + None, description="Code of the geographic unit (for countries, preferred = ISO3 code)", title="Location code" + ) + type: Optional[str] = Field( + None, description="Type of geographic unit e.g. country, state, region, province, town, etc", title="Type" + ) + + +class Keyword(SchemaBaseModel): + name: Optional[str] = Field(None, description="Keyword, composed of one or multiple words", title="Name") + vocabulary: Optional[str] = Field( + None, + description="Vocabulary name (for keywords extracted from controlled vocabularies)", + title="Vocabulary name", + ) + uri: Optional[str] = Field(None, title="Vocabulary URI") + + +class Theme(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Name") + parent_id: Optional[str] = Field(None, title="Parent Identifier") + vocabulary: Optional[str] = Field(None, description="Name of the controlled vocabulary", title="Vocabulary") + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the theme is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Topic(SchemaBaseModel): + id: str = Field(..., title="Unique identifier") + name: str = Field(..., title="Topic") + parent_id: Optional[str] = Field( + None, description="For subtopics, provide the ID of the parent topic", title="Parent topic identifier" + ) + vocabulary: Optional[str] = Field( + None, description="Name of the controlled vocabulary, if the topic is from a taxonomy.", title="Vocabulary name" + ) + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the topic is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Discipline(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Discipline title or name") + parent_id: Optional[str] = Field(None, description="Parent discipline ID", title="Parent discipline Identifier") + vocabulary: Optional[str] = Field(None, description="Vocabulary", title="Vocabulary") + uri: Optional[str] = Field(None, description="Website link", title="URI") + + +class RepositoryUriItem(SchemaBaseModel): + name: str = Field( + ..., + description="Name of the repository where code is hosted. e.g. `Github`, `Bitbucket`, etc.", + title="Repository name", + ) + type: Optional[str] = Field(None, description="Repo type e.g. `git`, `svn`, `other`", title="Type") + uri: Optional[Any] = Field(None, description="URI of the project repository", title="URI") + + +class LicenseItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="License") + uri: Optional[str] = Field(None, title="URI") + + +class Method(SchemaBaseModel): + name: Optional[str] = Field(None, title="Method name") + note: Optional[str] = Field(None, title="Description") + + +class SoftwareItem(SchemaBaseModel): + class Config: + extra = Extra.forbid + + name: Optional[str] = Field(None, title="Name") + version: Optional[str] = Field(None, title="Version") + library: Optional[List[str]] = Field( + None, description="Software-specific libraries or packages used", title="Libraries or packages used" + ) + + +class Author(SchemaBaseModel): + name: Optional[str] = Field(None, title="Person or organization name") + abbr: Optional[str] = Field(None, title="Abbreviation") + role: Optional[str] = Field(None, title="Role") + + +class LicenseItem1(SchemaBaseModel): + name: Optional[str] = Field(None, title="License name") + uri: Optional[str] = Field(None, title="License URI") + + +class Script(SchemaBaseModel): + file_name: Optional[str] = Field(None, title="File name") + zip_package: Optional[str] = Field( + None, description="Provide the name of the zip file, if the file is included in a zip", title="Zip file" + ) + title: str = Field(..., title="Title") + authors: Optional[List[Author]] = Field(None, description="Author(s) of the script", title="Authors") + date: Optional[str] = Field(None, title="Date") + format: Optional[str] = Field(None, title="Format") + software: Optional[str] = Field(None, title="Software") + description: Optional[str] = Field(None, title="Description") + methods: Optional[str] = Field(None, title="Methods") + dependencies: Optional[str] = Field(None, title="Dependencies") + instructions: Optional[str] = Field(None, title="Instructions or note for running the script") + source_code_repo: Optional[str] = Field(None, title="Source code repositor") + notes: Optional[str] = Field(None, title="Notes") + license: Optional[List[LicenseItem1]] = Field(None, title="License") + + +class Dataset(SchemaBaseModel): + name: Optional[str] = Field(None, title="Dataset name") + idno: Optional[str] = Field(None, description="unique ID of the dataset", title="Dataset ID") + note: Optional[str] = Field( + None, + description=( + "Brief description of the dataset (note: ideally, the dataset will be documented using a specific metadata" + " schema like the DDI)." + ), + title="Description", + ) + access_type: Optional[str] = Field(None, title="Data access policy") + license: Optional[str] = Field(None, title="License") + license_uri: Optional[str] = Field(None, title="License URI") + uri: Optional[str] = Field( + None, + description="Link to the website where the data may be accessed or more information on access can be found", + title="Dataset URI", + ) + + +class Contact(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + role: Optional[str] = Field(None, title="Role") + affiliation: Optional[str] = Field(None, title="Affiliation") + email: Optional[str] = Field(None, title="Email") + telephone: Optional[str] = Field(None, title="Telephone") + uri: Optional[str] = Field(None, title="URI") + + +class ProjectDesc(SchemaBaseModel): + """ + Description of the research project + """ + + title_statement: Optional[TitleStatement] = Field(None, description="Project title") + abstract: Optional[str] = Field(None, title="Abstract") + review_board: Optional[str] = Field( + None, + description=( + "Information on whether and when the project was submitted, reviewed, and approved by an institutional" + " review board (or independent ethics committee, ethical review board (ERB), research ethics board, or" + " equivalent)." + ), + title="Institutional review board", + ) + output: Optional[List[OutputItem]] = Field( + None, description="Description of outputs of the research project", title="Output" + ) + approval_process: Optional[List[ApprovalProces]] = Field( + None, description="A description of the project output review process", title="Approval process" + ) + project_website: Optional[List[str]] = Field(None, description="Project website link", title="Project website") + language: Optional[List[LanguageItem]] = Field( + None, description="Documentation language e.g. English, French, etc.", title="Language" + ) + production_date: Optional[List[str]] = Field( + None, + description=( + "Date in ISO format when the dissemination-ready version of the research project was produced. It can be a" + " year (YYYY), year-month (YYYY-MM), or year-month-day (YYYY-MM-DD)" + ), + title="Date of production (YYYY-MM-DD)", + ) + version_statement: Optional[VersionStatement] = Field( + None, description="Version statement", title="Version statement" + ) + errata: Optional[List[Erratum]] = Field( + None, description="List of corrected errors in data, scripts or output", title="Errata" + ) + process: Optional[List[Proces]] = Field( + None, + description=( + "A description, following a logical sequence, of the various phases of the research project implementation." + " This field may be used to document explorations steps that may have resulted in dead ends, to document" + " intermediary steps at which a project may have been reviewed and approved, etc." + ), + title="Process", + ) + authoring_entity: Optional[List[AuthoringEntityItem]] = Field( + None, + description=( + "The person, corporate body, or agency responsible for the project's substantive and intellectual content." + " Repeat the element for each author/primary investigator, and use 'affiliation' attribute if available." + " Invert first and last name and use commas." + ), + title="Authoring entity", + ) + contributors: Optional[List[Contributor]] = Field( + None, description="The person, corporate body, or agency who contributed to the project.", title="Contributors" + ) + sponsors: Optional[List[Sponsor]] = Field( + None, + description=( + "The source(s) of funds for production of the work. If different funding agencies sponsored different" + " stages of the production process, use the 'role' attribute to distinguish them." + ), + title="Sponsors / Funding agencies", + ) + curators: Optional[List[Curator]] = Field( + None, description="The person, corporate body, or agency who curated the project.", title="Curators" + ) + reviews_comments: Optional[List[ReviewsComment]] = None + acknowledgements: Optional[List[Acknowledgement]] = Field( + None, + description="Acknowledgments of persons or organizations (other than sponsors) who contributed to the project.", + title="Other acknowledgments", + ) + acknowledgement_statement: Optional[str] = Field( + None, description="Acknowledgement statement", title="Acknowledgement statement" + ) + disclaimer: Optional[str] = Field(None, title="Disclaimer") + confidentiality: Optional[str] = Field(None, title="Confidentiality") + citation_requirement: Optional[str] = Field( + None, description="Citation requirement (can include a specific recommended citation)" + ) + related_projects: Optional[List[RelatedProject]] = Field( + None, description="A list and bried description of related research projects", title="Related research projects" + ) + geographic_units: Optional[List[GeographicUnit]] = Field( + None, + description=( + "List of geographic locations (regions, countries, states, provinces, etc.) describing the geographic" + " coverahe of the research project." + ), + title="Geographic locations", + ) + keywords: Optional[List[Keyword]] = Field(None, title="Keywords") + themes: Optional[List[Theme]] = Field(None, description="Themes") + topics: Optional[List[Topic]] = Field( + None, + description=( + "Topics covered by the project (ideally, a controlled vocabulary should be used). This can be a" + " hierarchical list of topics." + ), + title="Topics", + ) + disciplines: Optional[List[Discipline]] = Field( + None, + description="Disciplines e.g. `Social sciences, economics`, `Natural sciences, biology`", + title="Disciplines", + ) + repository_uri: Optional[List[RepositoryUriItem]] = Field( + None, description="Source code repository", title="Source code repository" + ) + license: Optional[List[LicenseItem]] = Field( + None, + description=( + "Overall statement on license. Note: information on license specific to scripts and/or datasets should be" + " provided in the documentation of scripts and datasets." + ), + title="License", + ) + copyright: Optional[str] = Field(None, title="Copyright") + technology_environment: Optional[str] = Field( + None, + description="Notes about the technology environment used by the authors to implement the project", + title="Technology environment", + ) + technology_requirements: Optional[str] = Field( + None, + description="Software/hardware or other technology requirements needed to replicate the scripts", + title="Technology requirements", + ) + reproduction_instructions: Optional[str] = Field(None, description="Reproduction instructions") + methods: Optional[List[Method]] = Field( + None, description="Methods or algorithms applied", title="Methods or algorithms applied" + ) + software: Optional[List[SoftwareItem]] = Field( + None, description="List of software applications used for the project", title="Software" + ) + scripts: Optional[List[Script]] = Field(None, description="Description of each script file", title="Script files") + data_statement: Optional[str] = Field( + None, + description=( + "Overall statement on data used by the project. More detailed description of the datasets should be" + " provided in the 'datasets' field." + ), + ) + datasets: Optional[List[Dataset]] = Field( + None, description="List and description of datasets used by the research project", title="Datasets" + ) + contacts: Optional[List[Contact]] = Field(None, description="Contacts", title="Contacts") + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class ModelInfoItem(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + author: Optional[str] = Field(None, title="Author") + version: Optional[str] = Field(None, title="Version") + model_id: Optional[str] = Field(None, title="Model Identifier") + nb_topics: Optional[float] = Field(None, title="Number of topics") + description: Optional[str] = Field(None, title="Description") + corpus: Optional[str] = Field(None, title="Corpus name") + uri: Optional[str] = Field(None, title="URI") + + +class TopicWord(SchemaBaseModel): + word: Optional[str] = Field(None, title="Word") + word_weight: Optional[float] = Field(None, title="Word weight") + + +class TopicDescriptionItem(SchemaBaseModel): + topic_id: Optional[Union[int, str]] = Field(None, title="Topic identifier") + topic_score: Optional[Union[float, str]] = Field(None, title="Topic score") + topic_label: Optional[str] = Field(None, title="Topic label") + topic_words: Optional[List[TopicWord]] = Field(None, description="Words", title="Topic words") + + +class LdaTopic(SchemaBaseModel): + class Config: + extra = Extra.forbid + + model_info: Optional[List[ModelInfoItem]] = Field(None, title="Model information") + topic_description: Optional[List[TopicDescriptionItem]] = Field(None, title="Topic information") + + +class Embedding(SchemaBaseModel): + id: str = Field(..., title="Vector Model ID") + description: Optional[str] = Field(None, title="Vector Model Description") + date: Optional[str] = Field(None, title="Date (YYYY-MM-DD)") + vector: Dict[str, Any] = Field(..., title="Vector") + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class ResearchProjectSchemaDraft(SchemaBaseModel): + """ + Schema for documenting research projects and data analysis scripts + """ + + repositoryid: Optional[str] = Field( + None, + description="Abbreviation for the collection that owns the research project", + title="Collection ID that owns the project", + ) + published: Optional[int] = Field(0, description="Status of the project - 0=draft, 1=published", title="Status") + overwrite: Optional[Overwrite] = Field("no", description="Overwrite document if already exists?") + doc_desc: Optional[DocDesc] = Field( + None, + description="Document description; the Document is the file containing the structured metadata", + title="Document description", + ) + project_desc: Optional[ProjectDesc] = Field( + None, description="Description of the research project", title="Project description" + ) + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags (user-defined)") + lda_topics: Optional[List[LdaTopic]] = Field(None, description="LDA topics", title="LDA topics") + embeddings: Optional[List[Embedding]] = Field(None, description="Word embeddings", title="Word embeddings") + additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata") diff --git a/pydantic_schemas/table_schema.py b/pydantic_schemas/table_schema.py new file mode 100644 index 0000000..0795030 --- /dev/null +++ b/pydantic_schemas/table_schema.py @@ -0,0 +1,513 @@ +# generated by datamodel-codegen: +# filename: table-schema.json +# timestamp: 2024-09-13T19:00:29+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import Extra, Field + +from .utils.schema_base_model import SchemaBaseModel + + +class Overwrite(Enum): + """ + Overwrite document if already exists? + """ + + yes = "yes" + no = "no" + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class MetadataInformation(SchemaBaseModel): + """ + Document description + """ + + class Config: + extra = Extra.forbid + + idno: Optional[str] = Field(None, title="Unique ID number for the document") + title: Optional[str] = Field(None, title="Document title") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + production_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version: Optional[str] = Field( + None, description="Identify and describe the current version of the document", title="Document version" + ) + + +class TitleStatement(SchemaBaseModel): + """ + Title statement + """ + + idno: str = Field( + ..., + description=( + "The ID number of a dataset is a unique number that is used to identify a particular survey. Define and use" + " a consistent scheme to use. Such an ID could be constructed as follows:" + " country-producer-survey-year-version where \n - country is the 3-letter ISO country abbreviation \n -" + " producer is the abbreviation of the producing agency \n - survey is the survey abbreviation \n - year is" + " the reference year (or the year the survey started) \n - version is the number dataset version number" + " (see Version Description below)" + ), + title="Unique user defined ID", + ) + table_number: Optional[str] = Field(None, description="Table number", title="Table number") + title: str = Field(..., description="Table title", title="Table title") + sub_title: Optional[str] = Field(None, description="A short subtitle for the table", title="Table subtitle") + alternate_title: Optional[str] = Field( + None, + description="Any form of the title used as a substitute or alternative to the formal title of the resource.", + title="Abbreviation or Acronym", + ) + translated_title: Optional[str] = Field(None, title="Translated title") + + +class Identifier(SchemaBaseModel): + type: Optional[str] = Field( + None, description="Type of identifier e.g. `doi`, `handle`, `other`", title="Identifier type" + ) + identifier: str = Field(..., title="Identifier") + + +class PublisherItem(SchemaBaseModel): + name: str = Field(..., title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + abbreviation: Optional[str] = Field(None, title="Abbreviation") + role: Optional[str] = Field(None, title="Role") + uri: Optional[str] = Field(None, title="URI") + + +class TableColumn(SchemaBaseModel): + label: str = Field(..., title="Label") + var_name: Optional[str] = Field(None, description="Variable name", title="Variable name") + dataset: Optional[str] = Field( + None, description="Identifies the dataset that contains the variable reported in var_name", title="Dataset" + ) + + +class TableRow(TableColumn): + pass + + +class TableFootnote(SchemaBaseModel): + number: Optional[str] = Field(None, description="Footnote number", title="Footnote number") + text: str = Field(..., title="Footnote text") + + +class TableSery(SchemaBaseModel): + name: str = Field(..., description="Name", title="Name") + maintainer: Optional[str] = Field(None, title="Maintainer") + uri: Optional[str] = Field(None, title="URI") + description: Optional[str] = Field(None, title="Description") + + +class Statistic(SchemaBaseModel): + value: Optional[str] = Field(None, title="Value") + + +class UnitObservationItem(Statistic): + pass + + +class DataSource(SchemaBaseModel): + name: Optional[str] = Field( + None, + description=( + "The name (title) of the data source. For example, a table data may be extracted from the `Population" + " Census 2020`." + ), + title="Name", + ) + abbreviation: Optional[str] = Field( + None, description="The abbreviation (acronym) of the data source.", title="Abbreviation" + ) + source_id: Optional[str] = Field( + None, + description="A unique identifier for the source, such as a Digital Object Identifier (DOI).", + title="Source ID", + ) + note: Optional[str] = Field( + None, + description=( + "A note that describes how the source was used, possibly mentioning issues in the use of the source." + ), + title="Note", + ) + uri: Optional[str] = Field(None, description="A link (URL) to the source dataset.", title="URI") + + +class TimePeriod(SchemaBaseModel): + from_: str = Field( + ..., + alias="from", + description="Date in ISO format (YYYY-MM-DD). Partial dates are supported", + title="Start date", + ) + to: Optional[str] = Field( + None, description="Date in ISO format (YYYY-MM-DD). Partial dates are supported", title="End date" + ) + + +class UniverseItem(SchemaBaseModel): + value: Optional[str] = Field(None, title="Universe") + + +class RefCountryItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Country name") + code: Optional[str] = Field(None, title="Country code") + + +class GeographicUnit(SchemaBaseModel): + name: str = Field( + ..., description="Name of the geographic unit e.g. 'World', 'Africa', 'Afghanistan'", title="Location name" + ) + code: Optional[str] = Field( + None, description="Code of the geographic unit (for countries, preferred = ISO3 code)", title="Location code" + ) + type: Optional[str] = Field( + None, description="Type of geographic unit e.g. country, state, region, province etc", title="Type" + ) + + +class BboxItem(SchemaBaseModel): + west: Optional[str] = Field(None, title="West") + east: Optional[str] = Field(None, title="East") + south: Optional[str] = Field(None, title="South") + north: Optional[str] = Field(None, title="North") + + +class Language(SchemaBaseModel): + name: Optional[str] = Field(None, description="Language name", title="Name") + code: Optional[str] = Field(None, title="code") + + +class Link(SchemaBaseModel): + uri: Optional[str] = Field(None, title="URL") + description: Optional[str] = Field(None, title="Description") + + +class ApiDocumentationItem(SchemaBaseModel): + description: Optional[str] = Field(None, title="Description") + uri: Optional[str] = Field(None, title="URI") + + +class Publication(SchemaBaseModel): + title: Optional[str] = Field(None, title="Title") + uri: Optional[str] = Field(None, title="URL") + + +class Theme(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Name") + parent_id: Optional[str] = Field(None, title="Parent Identifier") + vocabulary: Optional[str] = Field(None, description="Name of the controlled vocabulary", title="Vocabulary") + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the theme is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Topic(SchemaBaseModel): + id: str = Field(..., title="Unique Identifier") + name: str = Field(..., title="Topic") + parent_id: Optional[str] = Field( + None, description="For subtopics, provide the ID of the parent topic", title="Parent topic Identifier" + ) + vocabulary: Optional[str] = Field( + None, description="Name of the controlled vocabulary, if the topic is from a taxonomy.", title="Vocabulary" + ) + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the topic is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Discipline(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Discipline title or name") + parent_id: Optional[str] = Field(None, description="Parent discipline ID", title="Parent discipline Identifier") + vocabulary: Optional[str] = Field(None, description="Vocabulary", title="Vocabulary") + uri: Optional[str] = Field(None, description="Website link", title="URI") + + +class Definition(SchemaBaseModel): + name: str = Field(..., title="Definition") + definition: Optional[str] = Field(None, description="Definition", title="Definition") + uri: Optional[str] = Field(None, description="Website link", title="URI") + + +class Classification(SchemaBaseModel): + name: str = Field(..., title="Classification name") + version: Optional[str] = Field(None, description="Version number", title="Version") + organization: Optional[str] = Field( + None, description="Organization responsible for the classification", title="Organization" + ) + uri: Optional[str] = Field(None, description="Website link", title="URI") + + +class LicenseItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="License") + uri: Optional[str] = Field(None, title="URI") + + +class Contact(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + role: Optional[str] = Field(None, title="Role") + affiliation: Optional[str] = Field(None, title="Affiliation") + email: Optional[str] = Field(None, title="Email") + telephone: Optional[str] = Field(None, title="Telephone") + uri: Optional[str] = Field(None, title="URI") + + +class Note(SchemaBaseModel): + note: Optional[str] = Field(None, title="Note") + + +class Type(Enum): + isPartOf = "isPartOf" + hasPart = "hasPart" + isVersionOf = "isVersionOf" + isFormatOf = "isFormatOf" + hasFormat = "hasFormat" + references = "references" + isReferencedBy = "isReferencedBy" + isBasedOn = "isBasedOn" + isBasisFor = "isBasisFor" + requires = "requires" + isRequiredBy = "isRequiredBy" + + +class Relation(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + type: Optional[Type] = Field(None, title="Type") + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class ModelInfoItem(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + author: Optional[str] = Field(None, title="Author") + version: Optional[str] = Field(None, title="Version") + model_id: Optional[str] = Field(None, title="Model Identifier") + nb_topics: Optional[float] = Field(None, title="Number of topics") + description: Optional[str] = Field(None, title="Description") + corpus: Optional[str] = Field(None, title="Corpus name") + uri: Optional[str] = Field(None, title="URI") + + +class TopicWord(SchemaBaseModel): + word: Optional[str] = Field(None, title="Word") + word_weight: Optional[float] = Field(None, title="Word weight") + + +class TopicDescriptionItem(SchemaBaseModel): + topic_id: Optional[Union[int, str]] = Field(None, title="Topic identifier") + topic_score: Optional[Union[float, str]] = Field(None, title="Topic score") + topic_label: Optional[str] = Field(None, title="Topic label") + topic_words: Optional[List[TopicWord]] = Field(None, description="Words", title="Topic words") + + +class LdaTopic(SchemaBaseModel): + class Config: + extra = Extra.forbid + + model_info: Optional[List[ModelInfoItem]] = Field(None, title="Model information") + topic_description: Optional[List[TopicDescriptionItem]] = Field(None, title="Topic information") + + +class Embedding(SchemaBaseModel): + id: str = Field(..., title="Vector Model ID") + description: Optional[str] = Field(None, title="Vector Model Description") + date: Optional[str] = Field(None, title="Date (YYYY-MM-DD)") + vector: Dict[str, Any] = Field(..., title="Vector") + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class AuthorIdItem(SchemaBaseModel): + type: Optional[Any] = Field(None, description="Source of identifier, e.g. ORCID", title="Type") + id: Optional[Any] = Field( + None, description="Author's unique identifier for the corresponding source", title="Identifier" + ) + + +class AuthoringEntityItem(SchemaBaseModel): + name: str = Field(..., title="Agency Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + abbreviation: Optional[str] = Field(None, title="Abbreviation") + uri: Optional[str] = Field(None, title="URI") + author_id: Optional[List[AuthorIdItem]] = Field( + None, + description="Unique identifier of an author, which may be provided by services like ORCID or other", + title="Author ID", + ) + + +class ContributorItem(PublisherItem): + pass + + +class KeywordItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + vocabulary: Optional[str] = Field(None, title="Vocabulary name") + uri: Optional[str] = Field(None, title="Vocabulary URI") + + +class TableDescription(SchemaBaseModel): + """ + Table Description + """ + + class Config: + extra = Extra.forbid + + title_statement: Optional[TitleStatement] = Field(None, description="Title statement") + identifiers: Optional[List[Identifier]] = Field(None, description="Other identifiers", title="Other identifiers") + authoring_entity: Optional[List[AuthoringEntityItem]] = Field( + None, description="Authoring entity", title="Authoring entity" + ) + contributors: Optional[List[ContributorItem]] = Field(None, description="Contributors", title="Contributors") + publisher: Optional[List[PublisherItem]] = Field(None, description="Publisher", title="Publisher") + date_created: Optional[str] = Field(None, description="Date created", title="Date created") + date_published: Optional[str] = Field(None, title="Date published") + date_modified: Optional[str] = Field( + None, description="Date on which the resource was changed.", title="Date last modified" + ) + version: Optional[str] = Field(None, title="Version") + description: Optional[str] = Field(None, description="Description", title="Description") + table_columns: Optional[List[TableColumn]] = Field( + None, description="List of table column names", title="Table column names" + ) + table_rows: Optional[List[TableRow]] = Field(None, description="Table row level data", title="Table row level data") + table_footnotes: Optional[List[TableFootnote]] = Field(None, description="Footnotes", title="Chart footnotes") + table_series: Optional[List[TableSery]] = Field(None, description="Table series", title="Table series") + statistics: Optional[List[Statistic]] = Field(None, title="Statistics") + unit_observation: Optional[List[UnitObservationItem]] = Field(None, title="Unit observation") + data_sources: Optional[List[DataSource]] = Field(None, title="Data sources") + time_periods: Optional[List[TimePeriod]] = Field(None, description="Time periods", title="Time periods") + universe: Optional[List[UniverseItem]] = Field(None, title="Universe") + ref_country: Optional[List[RefCountryItem]] = Field(None, title="Reference country") + geographic_units: Optional[List[GeographicUnit]] = Field( + None, + description=( + "List of geographic units (regions, countries, states, provinces, etc.) for which data are available in the" + " database." + ), + title="Geographic locations", + ) + geographic_granularity: Optional[str] = Field( + None, + description="Granularity of geographic coverage. examples `national`, `regional`, `provincial`", + title="Geographic granularity", + ) + bbox: Optional[List[BboxItem]] = Field(None, title="Geographic bounding box") + languages: Optional[List[Language]] = Field(None, description="languages") + links: Optional[List[Link]] = Field(None, title="Links") + api_documentation: Optional[List[ApiDocumentationItem]] = Field(None, description="API Documentation") + publications: Optional[List[Publication]] = Field(None, title="Publications") + keywords: Optional[List[KeywordItem]] = Field(None, description="Keywords", title="Keywords") + themes: Optional[List[Theme]] = Field(None, description="Themes") + topics: Optional[List[Topic]] = Field( + None, + description="Topics covered by the table (ideally, the list of topics will be a controlled vocabulary)", + title="Topics", + ) + disciplines: Optional[List[Discipline]] = Field( + None, + description="Disciplines e.g. `Social sciences, economics`, `Natural sciences, biology`", + title="Disciplines", + ) + definitions: Optional[List[Definition]] = Field( + None, description="Definitions or concepts covered by the table", title="Definitions" + ) + classifications: Optional[List[Classification]] = Field( + None, description="Classifications used in the table", title="Classifications" + ) + rights: Optional[str] = Field(None, title="Rights") + license: Optional[List[LicenseItem]] = Field(None, title="License") + citation: Optional[str] = Field(None, description="A bibliographic reference for the resource.", title="Citation") + confidentiality: Optional[str] = Field(None, title="Confidentiality") + sdc: Optional[str] = Field( + None, + description=( + "Information on statistical disclosure control measures applied to the table. This can include cell" + " suppression, or other techniques. Specialized packages have been developed for this purpose, like" + " [*sdcTable: Methods for Statistical Disclosure Control in Tabular" + " Data*](https://cran.r-project.org/web/packages/sdcTable/index.html) and" + " https://cran.r-project.org/web/packages/sdcTable/sdcTable.pdf \nThe information provided here should be" + " such that it does not provide intruders with useful information for reverse-engineering the protection" + " measures applied to the table." + ), + title="Statistical disclosure control", + ) + contacts: Optional[List[Contact]] = Field(None, description="Contacts", title="Contacts") + notes: Optional[List[Note]] = Field(None, title="Notes") + relations: Optional[List[Relation]] = Field(None, description="Related documents", title="Relations") + + +class Model(SchemaBaseModel): + """ + Draft Schema for Table data type + """ + + repositoryid: Optional[str] = Field( + None, + description="Abbreviation for the collection that owns the document", + title="Collection ID that owns the document", + ) + published: Optional[int] = Field(0, description="Status - 0=draft, 1=published", title="Status") + overwrite: Optional[Overwrite] = Field("no", description="Overwrite document if already exists?") + metadata_information: Optional[MetadataInformation] = Field( + None, description="Document description", title="Document metadata information" + ) + table_description: Optional[TableDescription] = Field( + None, description="Table Description", title="Table Description" + ) + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags") + lda_topics: Optional[List[LdaTopic]] = Field(None, description="LDA topics", title="LDA topics") + embeddings: Optional[List[Embedding]] = Field(None, description="Word embeddings", title="Word embeddings") + additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata") diff --git a/pydantic_schemas/tests/__init__.py b/pydantic_schemas/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pydantic_schemas/tests/test_metadata_manager.py b/pydantic_schemas/tests/test_metadata_manager.py new file mode 100644 index 0000000..426ab84 --- /dev/null +++ b/pydantic_schemas/tests/test_metadata_manager.py @@ -0,0 +1,92 @@ +import pytest + +from pydantic_schemas.metadata_manager import MetadataManager + + +@pytest.mark.parametrize( + "metadata_name", ["document", "script", "microdata", "table", "indicators_db", "indicator", "video"] +) +def test_metadata_by_name(tmpdir, metadata_name): + mm = MetadataManager() + assert metadata_name in mm.metadata_type_names + + for debug in [True, False]: + mm.create_metadata_outline(metadata_name_or_class=metadata_name, debug=debug) + + # Write empty metadata + filename = mm.write_metadata_outline_to_excel( + metadata_name_or_class=metadata_name, filename=tmpdir.join(f"test_{metadata_name}.xlsx"), title=metadata_name + ) + + # Read the metadata back + tmp = mm.read_metadata_from_excel(filename=filename) + + # Save the read metadata to a new file + filename2 = tmpdir.join(f"test_{metadata_name}_2.xlsx") + mm.save_metadata_to_excel(metadata_name_or_class=metadata_name, object=tmp, filename=filename2, title=metadata_name) + + # make an outline object + mm.create_metadata_outline(metadata_name_or_class=metadata_name) + + +@pytest.mark.parametrize( + "metadata_name", ["document", "script", "microdata", "table", "timeseries_db", "indicator", "video"] +) +def test_metadata_by_class(tmpdir, metadata_name): + mm = MetadataManager() + + metadata_class = mm.metadata_class_from_name(metadata_name=metadata_name) + + # outline from class + mm.create_metadata_outline(metadata_name_or_class=metadata_class) + + # write and read from class + filename_class = mm.write_metadata_outline_to_excel( + metadata_name_or_class=metadata_class, + filename=tmpdir.join(f"test_class_{metadata_name}.xlsx"), + title=metadata_name, + ) + mm.read_metadata_from_excel(filename=filename_class, metadata_class=metadata_class) + + +def test_standardize_metadata_name(): + mm = MetadataManager() + inputs = [ + "Document", + "SCRIPT", + "survey", + "survey-microdata", + "survey microdata", + "microdata", + "table", + "indicators-db", + "timeseries-db", + "INdicator", + "timeseries", + "VIdeo", + ] + + expecteds = [ + "document", + "script", + "microdata", + "microdata", + "microdata", + "microdata", + "table", + "indicators_db", + "indicators_db", + "indicator", + "indicator", + "video", + ] + + for inp, expected in zip(inputs, expecteds): + actual = mm.standardize_metadata_name(inp) + assert actual == expected, f"expected {expected} but got {actual}" + + with pytest.raises(NotImplementedError): + mm.standardize_metadata_name("Image") + + with pytest.raises(ValueError): + mm.standardize_metadata_name("Bad-name") diff --git a/pydantic_schemas/tests/test_pydantic_to_excel.py b/pydantic_schemas/tests/test_pydantic_to_excel.py new file mode 100644 index 0000000..e5884bc --- /dev/null +++ b/pydantic_schemas/tests/test_pydantic_to_excel.py @@ -0,0 +1,453 @@ +import os +from enum import Enum +from typing import Any, Dict, List, Optional + +import pandas as pd +import pytest +from pydantic import BaseModel, Field + +from pydantic_schemas.document_schema import ScriptSchemaDraft +from pydantic_schemas.indicator_schema import TimeseriesSchema +from pydantic_schemas.indicators_db_schema import TimeseriesDatabaseSchema + +# from pydantic_schemas.definitions.geospatial_schema import GeospatialSchema +# from pydantic_schemas.definitions.image_schema import ImageDataTypeSchema +from pydantic_schemas.microdata_schema import MicrodataSchema +from pydantic_schemas.script_schema import ResearchProjectSchemaDraft +from pydantic_schemas.table_schema import Model as TableModel +from pydantic_schemas.utils.excel_to_pydantic import ( + excel_doc_to_pydantic, + excel_sheet_to_pydantic, + excel_single_sheet_to_pydantic, +) +from pydantic_schemas.utils.pydantic_to_excel import ( + correct_column_widths, + create_sheet, + open_or_create_workbook, + shade_30_rows_and_protect_sheet, + shade_locked_cells, + write_across_many_sheets, + write_pydantic_to_sheet, + write_title_and_version_info, + write_to_single_sheet, +) +from pydantic_schemas.utils.quick_start import make_skeleton +from pydantic_schemas.video_schema import Model as VideoModel + + +def test_simple_schema(tmpdir, index_above=False): + class Simple(BaseModel): + idno: str + title: str + author: str + + simple_original = Simple(idno="AVal", title="BVal", author="CVal") + + filename = tmpdir.join(f"integration_test_simple_schema_.xlsx") + write_to_single_sheet(filename, simple_original, "simple_original", "Simple Metadata") + + parsed_simple = excel_sheet_to_pydantic(filename, "metadata", Simple) + assert parsed_simple == simple_original, parsed_simple + + +def test_two_layer_simple_schema(tmpdir, index_above=False): + class Production(BaseModel): + idno: str + title: str + author: str + + class Country(BaseModel): + name: str + initials: str + + class ProductionAndCountries(BaseModel): + production: Production + countries: Country + + inp = ProductionAndCountries( + production=Production(idno="AVal", title="BVal", author="CVal"), + countries=Country(name="MyCountry", initials="MC"), + ) + + filename = tmpdir.join(f"integration_test_two_layer_simple_schema.xlsx") + write_to_single_sheet(filename, inp, "ProductionAndCountries", "Production and Countries") + + parsed_outp = excel_sheet_to_pydantic(filename, "metadata", ProductionAndCountries) + assert parsed_outp == inp, parsed_outp + + +def test_multilayer_simple_schema(tmpdir): + class Production(BaseModel): + idno: str + title: str + author: str + + class Country(BaseModel): + name: str + initials: str + + class Language(BaseModel): + name: Optional[str] = Field(None, description="Language title", title="Name") + code: Optional[str] = Field(None, title="code") + + class Topic(BaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Topic") + + class SeriesDescription(BaseModel): + language: Language + topic: Topic + + class ProductionAndCountries(BaseModel): + production: Production + countries: Country + series_description: SeriesDescription + idno: str + title: Optional[str] = None + subtitle: Optional[str] = None + + series_description = SeriesDescription( + language=Language(name="English", code="EN"), topic=Topic(id="topic1", name="topic1") + ) + + inp = ProductionAndCountries( + production=Production(idno="AVal", title="BVal", author="CVal"), + countries=Country(name="MyCountry", initials="MC"), + series_description=series_description, + idno="example_idno", + title="example_title", + ) + + filename = tmpdir.join(f"integration_test_multilayer_simple_schema_.xlsx") + write_to_single_sheet(filename, inp, "ProductionAndCountries", "Production and Countries") + parsed_outp = excel_sheet_to_pydantic(filename, "metadata", ProductionAndCountries) + assert parsed_outp == inp, parsed_outp + + +def test_optional_missing_deprecated_new_simple(tmpdir): + class Production(BaseModel): + idno: Optional[str] = None + title: Optional[str] = None + subtitle: Optional[str] = None + author: str + deprecatedFeature: str + + original_production = Production(idno="", subtitle=None, author="author", deprecatedFeature="toberemoved") + + filename = tmpdir.join(f"integration_test_optional_missing_deprecated_new_simple_.xlsx") + write_to_single_sheet(filename, original_production, "Production", "Production") + + class Production(BaseModel): + idno: Optional[str] = None + title: Optional[str] = None + author: str + newFeature: Optional[str] = None + requiredNewFeature: str + + new_production = excel_sheet_to_pydantic(filename=filename, sheetname="metadata", model_type=Production) + assert new_production.idno is None + assert new_production.title is None + assert new_production.author == "author" + assert new_production.newFeature is None + assert new_production.requiredNewFeature == "" + + +def test_optional_missing_deprecated_new_two_level(tmpdir): + class Production(BaseModel): + idno: Optional[str] = None + title: Optional[str] = None + subtitle: Optional[str] = None + author: str + deprecatedFeature: str + + class Country(BaseModel): + name: str + initials: str + + class ProductionAndCountries(BaseModel): + production: Production + countries: Country + + example_production = Production(idno="", subtitle=None, author="author", deprecatedFeature="toberemoved") + example_country = Country(name="MadeupCountry", initials="MC") + example_production_and_country = ProductionAndCountries(production=example_production, countries=example_country) + + filename = tmpdir.join(f"integration_test_optional_missing_deprecated_new_two_level_.xlsx") + + write_to_single_sheet( + filename, example_production_and_country, "ProductionAndCountries", "Production and Countries" + ) + + class Production(BaseModel): + idno: Optional[str] = None + title: Optional[str] = None + author: str + newFeature: Optional[str] = None + requiredNewFeature: str + + class NewTopLevel(BaseModel): + val1: str + val2: str + + class ProductionAndCountries(BaseModel): + production: Production + countries: Country + newTopLevelFeature: Optional[NewTopLevel] = None + + new_pandc = excel_sheet_to_pydantic(filename=filename, sheetname="metadata", model_type=ProductionAndCountries) + assert new_pandc.production.idno is None + assert new_pandc.production.title is None + assert new_pandc.production.author == "author" + assert new_pandc.production.newFeature is None + assert new_pandc.production.requiredNewFeature == "" + assert new_pandc.countries.name == "MadeupCountry" + assert new_pandc.countries.initials == "MC" + + +def test_lists(tmpdir): + class ContactMethod(Enum): + phone = "PHONE" + email = "EMAIL" + + class Contact(BaseModel): + method: Optional[ContactMethod] = None + contact_address: Optional[str] = None + + class Person(BaseModel): + name: str + affiliations: Optional[List[str]] = None + contact_details: Optional[List[Contact]] = None + + class Production(BaseModel): + idno: Optional[str] = None + title: Optional[str] = None + authors: List[Person] + + class Country(BaseModel): + name: str + initials: str + + class ProductionAndCountries(BaseModel): + production: Production + countries: List[Country] + dates: List[str] + other: List[str] + otherOptional: Optional[List[str]] = None + + author0 = Person(name="person_0") + author1 = Person( + name="person_1", + affiliations=["Org1", "Org2"], + contact_details=[ + Contact(method=ContactMethod.email, contact_address="example@example.com"), + Contact(method=ContactMethod.phone, contact_address="123456789"), + ], + ) + author2 = Person(name="person_2", contact_details=[Contact()]) + author3 = Person(name="person_3", affiliations=["Org3"]) + example_production = Production(idno="", authors=[author0, author1, author2, author3]) + example_country = Country(name="MadeupCountry", initials="MC") + example_other_country = Country(name="MadeupCountry2", initials="MC2") + example_dates = ["April", "May", "June"] + example_production_and_country = ProductionAndCountries( + production=example_production, + countries=[example_country, example_other_country], + dates=["April", "May", "June"], + other=[], + otherOptional=None, + ) + + filename = tmpdir.join(f"integration_test_lists_.xlsx") + # filename = "integration_test_lists_.xlsx" + write_to_single_sheet( + filename, example_production_and_country, "ProductionAndCountries", "Production and Countries" + ) + + new_pandc = excel_sheet_to_pydantic(filename=filename, sheetname="metadata", model_type=ProductionAndCountries) + assert new_pandc.production.idno is None + assert new_pandc.production.title is None + assert len(new_pandc.production.authors) == 4 + assert author0 in new_pandc.production.authors + assert author1 in new_pandc.production.authors + assert author2 in new_pandc.production.authors + assert author3 in new_pandc.production.authors + assert len(new_pandc.countries) == 2 + assert example_country in new_pandc.countries + assert example_other_country in new_pandc.countries + assert new_pandc.dates == example_dates + assert new_pandc.other == [] + assert new_pandc.otherOptional is None or new_pandc.otherOptional == [] + + +def test_metadata_over_several_sheets(tmpdir): + class Person(BaseModel): + name: str + affiliations: Optional[List[str]] = None + + class Production(BaseModel): + idno: Optional[str] = None + title: Optional[str] = None + authors: List[Person] + + class Country(BaseModel): + name: str + initials: str + + class ProductionAndCountries(BaseModel): + production: Production + countries: List[Country] + dates: List[str] + other: List[str] + otherOptional: Optional[List[str]] = None + single_val: str + + author0 = Person(name="person_0") + author1 = Person(name="person_1", affiliations=["Org1", "Org2"]) + author2 = Person(name="person_2") + author3 = Person(name="person_3", affiliations=["Org3"]) + example_production = Production(idno="myidno", authors=[author0, author1, author2, author3]) + example_country = Country(name="MadeupCountry", initials="MC") + example_other_country = Country(name="MadeupCountry2", initials="MC2") + example_dates = ["April", "May", "June"] + example_production_and_country = ProductionAndCountries( + production=example_production, + countries=[example_country, example_other_country], + dates=example_dates, + other=["12"], + otherOptional=None, + single_val="single", + ) + + filename = tmpdir.join(f"integration_test_optional_missing_deprecated_new_two_level_.xlsx") + # filename = f"integration_test_optional_missing_deprecated_new_two_level_.xlsx" + write_across_many_sheets( + filename, example_production_and_country, "ProductionAndCountries", "Production and Countries" + ) + + new_pandc = excel_doc_to_pydantic(filename, ProductionAndCountries) + assert new_pandc.production.idno == "myidno" + assert new_pandc.production.title is None + assert len(new_pandc.production.authors) == 4 + assert author1 in new_pandc.production.authors + assert author1 in new_pandc.production.authors + assert author2 in new_pandc.production.authors + assert author3 in new_pandc.production.authors + assert len(new_pandc.countries) == 2 + assert example_country in new_pandc.countries + assert example_other_country in new_pandc.countries + assert new_pandc.dates == example_dates + assert new_pandc.other == ["12"] + assert new_pandc.otherOptional is None or new_pandc.otherOptional == [] + assert new_pandc.single_val == "single" + + +def test_dictionaries(tmpdir): + class SubDict(BaseModel): + sub_additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata at a lower level") + + class WithDict(BaseModel): + additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata") + optional_dict: Optional[Dict[str, Any]] = None + sub: SubDict + + wd = WithDict(additional={"s": "sa", "a": "va"}, sub=SubDict(sub_additional={"sub": "subval", "sub2": "subval2"})) + filename = tmpdir.join(f"integration_test_dictionaries_.xlsx") + write_across_many_sheets(filename, wd, "WithDict", "Looking at dictionaries") + + parsed_outp = excel_doc_to_pydantic(filename, WithDict) + assert parsed_outp == wd, parsed_outp + + +NAME_TO_TYPE = { + "Document": (ScriptSchemaDraft, write_across_many_sheets, excel_doc_to_pydantic), + # "Geospatial":GeospatialSchema, + # "Image":ImageDataTypeSchema, + "Survey": (MicrodataSchema, write_across_many_sheets, excel_doc_to_pydantic), + "Script": (ResearchProjectSchemaDraft, write_across_many_sheets, excel_doc_to_pydantic), + "Table": (TableModel, write_across_many_sheets, excel_doc_to_pydantic), + "Timeseries_DB": ( + TimeseriesDatabaseSchema, + write_to_single_sheet, + excel_single_sheet_to_pydantic, + ), # could be one sheet + "Timeseries": (TimeseriesSchema, write_across_many_sheets, excel_doc_to_pydantic), + "Video": (VideoModel, write_to_single_sheet, excel_single_sheet_to_pydantic), # could be one sheet +} + + +@pytest.mark.parametrize("name, type_writer_reader", [(k, v) for k, v in NAME_TO_TYPE.items()]) +def test_write_real_skeleton(tmpdir, name, type_writer_reader): + type, writer, reader = type_writer_reader + # folder = "excel_sheets" + filename = os.path.join(tmpdir, f"{name}_metadata.xlsx") + if os.path.exists(filename): + os.remove(filename) + ob = make_skeleton(type) + + writer(filename, ob, name, f"{name} Metadata") + reader(filename, type) + + +def test_demo(): + filename = "demo_output.xlsx" + sheet_title = "Formatting metadata examples" + + class SingleLevelData(BaseModel): + title: Optional[str] = None + author: str + + class Country(BaseModel): + name: str + initials: str + list_of_alternative_names: Optional[List[str]] = None + + class Description(BaseModel): + statement: str + abstract: str + + class MultiLevelAndListData(BaseModel): + description: Description + countries: List[Country] + organization: str + + class SubObject(BaseModel): + a: str + b: str + + class MetaDataOfVariousHierarchies(BaseModel): + idno: Optional[str] = None + database_name: Optional[str] = None + single_level_data: SingleLevelData + multi_level_data: MultiLevelAndListData + top_level_list: List[str] + top_level_optional_list: Optional[List[str]] = None + top_level_list_of_pydantic_objects: List[SubObject] + dictionary: Dict[str, str] + + example = MetaDataOfVariousHierarchies( + single_level_data=SingleLevelData(title="Metadata demo", author="FirstName LastName"), + multi_level_data=MultiLevelAndListData( + description=Description(statement="Data can be hierarchical", abstract="like this"), + countries=[ + Country(name="MyCountry", initials="MC", list_of_alternative_names=["And Lists", "can have lists"]), + Country(name="YourCountry", initials="YC"), + ], + organization="Example Org", + ), + top_level_list=["a", "b"], + top_level_list_of_pydantic_objects=[SubObject(a="a", b="b")], + dictionary={"example_key": "example_value"}, + ) + + if os.path.exists(filename): + os.remove(filename) + + write_to_single_sheet(filename, example, "MetaDataOfVariousHierarchies", sheet_title) + + # current_row = create_sheet_and_write_title(filename, sheetname, sheet_title) + # current_row = write_nested_simple_pydantic_to_sheet(filename, sheetname, example, current_row + 1) + # worksheet = open_or_create_workbook(filename) + # correct_column_widths(worksheet, sheet_name=sheetname) + # shade_30_rows_and_protect_sheet(worksheet, sheetname, current_row + 1) + # shade_locked_cells(worksheet, sheetname) + # worksheet.save(filename) diff --git a/pydantic_schemas/tests/test_quick_start.py b/pydantic_schemas/tests/test_quick_start.py new file mode 100644 index 0000000..9d53dc2 --- /dev/null +++ b/pydantic_schemas/tests/test_quick_start.py @@ -0,0 +1,243 @@ +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +import pytest +from pydantic import AnyUrl, BaseModel, Field, confloat + +from pydantic_schemas.metadata_manager import MetadataManager +from pydantic_schemas.utils.quick_start import DEFAULT_URL, make_skeleton # create_empty_schema_from_path, + + +def test_simple_strings(): + class Simple(BaseModel): + a: str + b: str + + expected = Simple(a="", b="") + actual = make_skeleton(Simple) + assert expected == actual + + +def test_simple_optional_string(): + class SimpleOptional(BaseModel): + a: Optional[str] + b: str + + expected = SimpleOptional(a=None, b="") + actual = make_skeleton(SimpleOptional) + assert expected == actual, actual + + +def test_simple_enum(): + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + class SimpleEnum(BaseModel): + a: Color + + expected = SimpleEnum(a=Color.RED) + actual = make_skeleton(SimpleEnum) + assert actual == expected, actual + + +def test_optional_enum(): + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + class SimpleEnum(BaseModel): + a: Optional[Color] = None + + expected = SimpleEnum(a=None) + actual = make_skeleton(SimpleEnum) + assert actual == expected, actual + + +def test_one_level(): + class Simple(BaseModel): + a: str + b: str + + class OneLevel(BaseModel): + c: Simple + + expected = OneLevel(c=Simple(a="", b="")) + actual = make_skeleton(OneLevel) + assert actual == expected + + +def test_one_level_optional(): + class Simple(BaseModel): + a: str + b: str + + class OneLevel(BaseModel): + c: Optional[Simple] + + expected = OneLevel(c=Simple(a="", b="")) + actual = make_skeleton(OneLevel) + assert actual == expected, actual + + +def test_two_levels(): + class Simple(BaseModel): + a: str + b: str + + class OneLevel(BaseModel): + c: Optional[Simple] = None + c1: Optional[str] = None + c2: str + + class TwoLevel(BaseModel): + d: OneLevel + e: Optional[OneLevel] = None + f: Optional[str] + + expected = TwoLevel( + d=OneLevel(c=Simple(a="", b=""), c1=None, c2=""), e=OneLevel(c=Simple(a="", b=""), c1=None, c2=""), f=None + ) + actual = make_skeleton(TwoLevel) + assert actual == expected, actual + + +def test_list_of_builtin(): + class Simple(BaseModel): + a: List[str] + b: str + + expected = Simple(a=[""], b="") + actual = make_skeleton(Simple, debug=True) + assert actual == expected, actual + + +def test_list_of_enum(): + class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + class Simple(BaseModel): + a: List[Color] + b: str + + expected = Simple(a=[Color.RED], b="") + actual = make_skeleton(Simple) + assert actual == expected, actual + + +def test_list_of_pydantic(): + class Simple(BaseModel): + a: str + b: str + + class OneLevel(BaseModel): + c: List[Simple] + + expected = OneLevel(c=[Simple(a="", b="")]) + actual = make_skeleton(OneLevel) + assert actual == expected, actual + + +def test_dict_of_strs(): + class Simple(BaseModel): + a: Dict[str, str] + b: str + + expected = Simple(a={"": ""}, b="") + actual = make_skeleton(Simple) + assert actual == expected, actual + + +def test_union_of_str_list_str(): + class Simple(BaseModel): + a: Optional[Union[str, List[str]]] = None + b: Any + c: str + + expected = Simple(a=[], b="", c="") + actual = make_skeleton(Simple) + assert actual == expected, actual + + class SimpleReversed(BaseModel): + a: Optional[Union[List[str], str]] = None + b: Any + c: str + + expected = SimpleReversed(a=[], b="", c="") + actual = make_skeleton(SimpleReversed) + assert actual == expected, actual + + +def test_union_of_List_Dict(): + class Simple(BaseModel): + a: Union[Dict[str, str], List[Any]] + + expected = Simple(a={"": ""}) + actual = make_skeleton(Simple, debug=True) + assert actual == expected, actual + + # because a dict is more complicated than a list, we should default to using the dict + class Simple(BaseModel): + a: Union[List[Any], Dict[str, str]] + + expected = Simple(a={"": ""}) + actual = make_skeleton(Simple, debug=True) + assert actual == expected, actual + + +def test_union_of_many_builtins(): + class Simple(BaseModel): + a: Union[int, float, str] + + expected = Simple(a="") + actual = make_skeleton(Simple, debug=True) + assert actual == expected, actual + + +def test_constriainedfloat(): + class GeographicBoundingBox(BaseModel): + westBoundLongitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="West") + eastBoundLongitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="East") + southBoundLatitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="South") + northBoundLatitude: Optional[confloat(ge=-180.0, le=180.0)] = Field(None, title="North") + + class GeographicElementItem(BaseModel): + geographicBoundingBox: Optional[GeographicBoundingBox] = Field(None, title="Geographic Bounding Box") + + expected = GeographicElementItem(geographicBoundingBox=GeographicBoundingBox()) + actual = make_skeleton(GeographicElementItem, debug=True) + assert actual == expected, actual + + +def test_url(): + class RegistryEntry(BaseModel): + role: Optional[AnyUrl] = None + assetIdentifier: str = Field(...) + registryIdentifier: AnyUrl = Field(...) + + expected = RegistryEntry(role=None, assetIdentifier="", registryIdentifier=DEFAULT_URL) + actual = make_skeleton(RegistryEntry, debug=True) + assert actual == expected, actual + + +def test_fieldname_is_protected(): + class BadFieldNames(BaseModel): + from_: str = Field(..., alias="from") + # import_: str + other: str + + expected = BadFieldNames(**{"from": "", "other": ""}) + actual = make_skeleton(BadFieldNames, debug=True) + assert actual == expected, actual + + +@pytest.mark.parametrize("n", [n for n in MetadataManager().metadata_type_names]) +def test_actual_schemas(n): + if n == "geospatial": + return + klass = MetadataManager().metadata_class_from_name(n) + make_skeleton(klass) diff --git a/pydantic_schemas/utils/__init__.py b/pydantic_schemas/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pydantic_schemas/utils/excel_to_pydantic.py b/pydantic_schemas/utils/excel_to_pydantic.py new file mode 100644 index 0000000..124b2e7 --- /dev/null +++ b/pydantic_schemas/utils/excel_to_pydantic.py @@ -0,0 +1,324 @@ +import json +import warnings +from typing import Any, List, Optional, Type, Union, get_args + +import numpy as np +import pandas as pd +from pydantic import BaseModel, create_model + +from .quick_start import make_skeleton +from .utils import ( + annotation_contains_pydantic, + get_subtype_of_optional_or_list, + is_dict_annotation, + is_list_annotation, + is_optional_annotation, + seperate_simple_from_pydantic, + subset_pydantic_model_type, +) + + +def find_string_and_count_nans(arr, search_str): + """ + Finds the index of the first occurrence of a string in a NumPy array, + and counts the number of NaNs immediately following that string. + + Args: + - arr (np.ndarray): The NumPy array to search in. + - search_str (str): The string to search for. + + Returns: + - tuple: (index, nan_count) + - index (int): Index of the first occurrence of the string, or -1 if not found. + - nan_count (int): Number of NaNs immediately following the string. + """ + index = np.where(arr == search_str)[0] # Find index of the first occurrence + if len(index) == 0: + return -1, 0 # Return -1 if string not found, nan_count is 0 + index = index[0] # Take the first occurrence index + nan_count = 0 + for i in range(index + 1, len(arr)): + if pd.isna(arr[i]): + nan_count += 1 + else: + break + return int(index), nan_count + + +# def is_horizontally_organized(m: Type[BaseModel], df: pd.DataFrame): +# """True if the index is along the top with the values below. False if the index is on the left side with the values to the right""" +# rows, cols = df.shape +# if rows == 1: +# return True +# elif cols == 1: +# return False + +# print("is_horizontally_organized is looking at ", m) +# if is_list_annotation(m): +# m = get_subtype_of_optional_or_list(m) +# expected_fields = m.model_json_schema()["properties"].keys() +# fields_if_horizontally_arranged = df.iloc[0, :].values +# fields_if_vertically_arranged = df.iloc[:, 0].values + +# horizontal_intersection = len(set(expected_fields).intersection(fields_if_horizontally_arranged)) +# vertical_intersection = len(set(expected_fields).intersection(fields_if_vertically_arranged)) +# return horizontal_intersection > vertical_intersection + + +def get_relevant_sub_frame(m: Type[BaseModel], df: pd.DataFrame, name_of_field: Optional[str] = None, debug=False): + """ + THe dataframe likely contains lots and lots of information about other models. + + THis function obtains only that information that pertains to this model + """ + names = df.iloc[:, 0].values + try: + name_of_class = m.model_json_schema()["title"] + + idx, sze = find_string_and_count_nans(names, name_of_class) + except AttributeError: + idx = -1 + sze = 0 + if idx < 0: + if name_of_field is not None: + idx, sze = find_string_and_count_nans(names, name_of_field) + if idx < 0: + error_message = f"'{m}' " + if name_of_field is not None: + error_message += f"and '{name_of_field}' " + error_message += f"not found in {names}" + raise IndexError(error_message) + + sub = df.iloc[idx : idx + sze + 1, 1:] + + sub = sub.dropna(how="all", axis=0) # drop all null rows + sub = sub.dropna(how="all", axis=1) # drop all null columns + if debug: + print("SubFrame = \n", sub) + # if is_horizontally_organized(m, sub): + # sub = sub.T + return sub + + +def handle_optional(name, annotation, df, from_within_list: bool = False, debug=False): + args = [a for a in get_args(annotation) if a is not type(None)] + # assert len(args) == 1, f"handle_optional encountered {args}" + if len(args) > 1: + if str in args: + arg = str + elif float in args: + arg = float + else: + arg = args[0] + else: + arg = args[0] + ret = annotation_switch(name, arg, df, from_within_list=from_within_list) + if debug: + print(f"optional ret: {ret}") + print(f"isinstance(ret, list): {isinstance(ret, list)}") + # print(f"len(ret): {len(ret)}") + if (isinstance(ret, list) or isinstance(ret, dict)) and len(ret) == 0: + return None + elif isinstance(ret, str) and ret == "": + return None + else: + return ret + + +def handle_list(name, anno, df, debug=False): + subtype = get_subtype_of_optional_or_list(anno) + if isinstance(subtype, type(BaseModel)): + try: + subframe = get_relevant_sub_frame(subtype, df, name_of_field=name) + except IndexError: + return [] + list_of_subs = [] + for c in subframe.columns[1:]: + subsubframe = subframe.loc[:, [subframe.columns[0], c]] + if debug: + print("subsubframe") + print(subsubframe) + print() + sub = instantiate_pydantic_object(model_type=subtype, df=subsubframe, from_within_list=True) + if debug: + print(f"instantiated: {sub}") + list_of_subs.append(sub) + return list_of_subs + # raise NotImplementedError(f"handle_list - {name}, {anno}, {subframe}") + else: + values = df.set_index(df.columns[0]).loc[name] + if debug: + print(f"handle_list anno:{anno}, value: {values}") + return [v for v in values if v is not None] + + +def handle_list_within_list(name, anno, df, debug=False): + if debug: + print(f"handle_list_within_list {name}, {anno}") + print(df) + values = df.set_index(df.columns[0]).loc[name, df.columns[1]] + if debug: + print(f"values: {values}, {type(values)}") + if values is None: + return [] + values = json.loads(values.replace("'", '"').replace("None", "null")) + if len(values) == 0: + return [] + sub_type = get_subtype_of_optional_or_list(anno) + if isinstance(values[0], dict) and annotation_contains_pydantic(sub_type): + return [sub_type(**v) for v in values] + elif not isinstance(values[0], dict) and not annotation_contains_pydantic(sub_type): + return [sub_type(v) for v in values] + else: + raise NotImplementedError(f"handle_list_within_list unexpected values - {name}, {anno}, {values}, {df}") + + +def handle_builtin_or_enum(name, anno, df, debug=False): + if debug: + print(df) + if len(df) == 0: + return "" + df_indexed = df.set_index(df.columns[0]) + if debug: + print("handle_builtin_or_enum", df_indexed) + # return df_indexed.loc[name, df.columns[1]] + if name not in df_indexed.index: + return "" + values = df_indexed.loc[name] + if len(values) == 0: + return "" + values = [v for v in values if v is not None] + if len(values) == 0: + return "" + elif len(values) >= 2: + raise ValueError(f"Expected only a single value but got {values}") + else: + return values[0] + + +def handle_dict(name, anno, df): + dictionary_type = create_model(name, **{"key": (Optional[List[str]], None), "value": (Optional[List[Any]], None)}) + dict_results = annotation_switch(name, dictionary_type, df) + if ( + dict_results.key is None + or len(dict_results.key) == 0 + or dict_results.value is None + or len(dict_results.value) == 0 + ): + return {} + else: + ret = {k: v for k, v in zip(dict_results.key, dict_results.value) if k is not None} + return ret + # raise NotImplementedError(f"Dictionary: {name}, {anno}, {dict_results}, {ret}") + + +def annotation_switch(name: str, anno, df: pd.DataFrame, from_within_list=False, debug=False) -> Any: + if debug: + print(f"annotation_to_value name: {name}") + if is_optional_annotation(anno): + if debug: + print("optional") + return handle_optional(name, anno, df, from_within_list=from_within_list) + elif is_dict_annotation(anno): + return handle_dict(name, anno, df) + elif is_list_annotation(anno): + if from_within_list: + if debug: + print("list within a list") + return handle_list_within_list(name, anno, df) + else: + if debug: + print("list") + return handle_list(name, anno, df) + elif isinstance(anno, type(BaseModel)): + if debug: + print("pydantic") + try: + sub = get_relevant_sub_frame(anno, df, name_of_field=name) + except IndexError: + return make_skeleton(anno) + return instantiate_pydantic_object(anno, sub) + elif len(get_args(anno)) == 0: + if debug: + print("builtin or enum") + return handle_builtin_or_enum(name, anno, df) + else: + raise NotImplementedError(anno) + + +def instantiate_pydantic_object( + model_type: Type[BaseModel], df: pd.DataFrame, from_within_list=False, debug=False +) -> BaseModel: + ret = {} + if debug: + print(f"instantiate_pydantic_object df = {df}") + for field_name, field_info in model_type.model_fields.items(): + anno = field_info.annotation + if debug: + print(f"Instantiating field {field_name}, anno {anno} and args {get_args(anno)}") + ret[field_name] = annotation_switch(field_name, anno, df, from_within_list=from_within_list) + if debug: + print(ret[field_name]) + print() + return model_type(**ret) + + +def excel_sheet_to_pydantic( + filename: str, sheetname: str, model_type: Union[Type[BaseModel], Type[List[BaseModel]]], debug=False +): + df = pd.read_excel(filename, sheet_name=sheetname, header=None) + df = df.where(df.notnull(), None) + if sheetname != "metadata": + try: + df = get_relevant_sub_frame(model_type, df) + except (KeyError, IndexError): + pass + + if is_optional_annotation(model_type): + return handle_optional(df.iloc[0, 0], model_type, df) + + if is_list_annotation(model_type): + return handle_list(df.iloc[0, 0], model_type, df) + + children = seperate_simple_from_pydantic(model_type) + ret = {} + if "simple" in children and len(children["simple"]): + sub = get_relevant_sub_frame(model_type, df, name_of_field=df.iloc[0, 0]) + simple_child_field_type = subset_pydantic_model_type(model_type, children["simple"]) + fields = instantiate_pydantic_object(simple_child_field_type, sub, debug=debug) + for child in children["simple"]: + ret[child] = getattr(fields, child) + for name in children["pydantic"]: + if debug: + print(f"Looking to get {name}") + anno = model_type.model_fields[name].annotation + ret[name] = annotation_switch(name, anno, df) + for k, v in ret.items(): + if isinstance(v, list) or isinstance(v, np.ndarray): + ret[k] = [elem for elem in v if elem is not None] + if debug: + print(ret) + + return model_type(**ret) + + +def excel_single_sheet_to_pydantic(filename: str, model_type: Type[BaseModel], verbose=False) -> BaseModel: + return excel_sheet_to_pydantic(filename, "metadata", model_type, debug=verbose) + + +def excel_doc_to_pydantic(filename: str, model_type: Type[BaseModel], verbose=False) -> BaseModel: + children = seperate_simple_from_pydantic(model_type) + annotations = {k: v.annotation for k, v in model_type.model_fields.items()} + ret = {} + + if len(children["simple"]) > 0: + field_type = subset_pydantic_model_type(model_type, children["simple"]) + fields = excel_sheet_to_pydantic(filename, sheetname="metadata", model_type=field_type, debug=verbose) + for child in children["simple"]: + ret[child] = getattr(fields, child) + for fieldname in children["pydantic"]: + if verbose: + print(f"Looking to get {fieldname}") + field_type = annotations[fieldname] + ret[fieldname] = excel_sheet_to_pydantic(filename, sheetname=fieldname, model_type=field_type, debug=verbose) + return model_type(**ret) diff --git a/pydantic_schemas/utils/pydantic_to_excel.py b/pydantic_schemas/utils/pydantic_to_excel.py new file mode 100644 index 0000000..f10e0c6 --- /dev/null +++ b/pydantic_schemas/utils/pydantic_to_excel.py @@ -0,0 +1,475 @@ +import copy +import json +import os +from enum import Enum +from typing import List, Optional, Tuple, Union + +import pandas as pd +from openpyxl import Workbook, load_workbook +from openpyxl.styles import Alignment, Border, Font, PatternFill, Protection, Side +from openpyxl.utils.dataframe import dataframe_to_rows +from openpyxl.worksheet.datavalidation import DataValidation +from openpyxl.worksheet.protection import SheetProtection +from openpyxl.worksheet.worksheet import Worksheet +from pydantic import BaseModel + +from .utils import ( + annotation_contains_dict, + annotation_contains_list, + assert_dict_annotation_is_strings_or_any, + get_subtype_of_optional_or_list, + seperate_simple_from_pydantic, + subset_pydantic_model, +) + +MAXCOL = 30 + + +def unprotect_cell(sheet, row, column): + sheet.cell(row=row, column=column).protection = Protection(locked=False) + + +def unprotect_row(sheet, row, colmin: int, colmax: Optional[int] = None): + if colmax is None: + colmax = max(colmin, MAXCOL, sheet.max_column) + for col in range(colmin, colmax + 1): + unprotect_cell(sheet, row, col) + + +def unprotect_given_col(sheet, col: int, rowmin: int, rowmax: int): + for row in range(rowmin, rowmax): + unprotect_cell(sheet, row, col) + + +def protect_and_shade_given_cell(sheet, row: int, col: int): + grey_fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid") + sheet.cell(row=row, column=col).fill = grey_fill + sheet.cell(row=row, column=col).protection = Protection(locked=True) + + +def protect_and_shade_row(sheet, row: int, colmin: int = 1, colmax: Optional[int] = None): + if colmax is None: + colmax = max(colmin, MAXCOL, sheet.max_column) + for col in range(colmin, colmax): + protect_and_shade_given_cell(sheet, row, col) + + +def protect_and_shade_col(sheet, col: int, rowmin: int, rowmax: int): + for row in range(rowmin, rowmax): + protect_and_shade_given_cell(sheet, row, col) + + +def shade_locked_cells(worksheet: Worksheet): + """ + Shades every cell grey if it is locked and leaves it unshaded if it is not locked. + + Args: + worksheet (Worksheet): The openPyxl Worksheet from an Excel file. + sheet_name (str): The name of the sheet to apply the shading. + """ + # Define the grey fill + grey_fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid") + + # Iterate through each cell in the worksheet + for row in worksheet.iter_rows(): + for cell in row: + if cell.protection.locked: + cell.fill = grey_fill + else: + cell.fill = PatternFill() # Remove any fill (reset to default) + + +def correct_column_widths(worksheet: Worksheet): + """ + Adjusts the column widths of an Excel sheet based on the maximum length of the content in each column. + If a column has no filled values, its width remains unchanged. + + Args: + workbook (Workbook): The openPyxl Workbook of an Excel file. + sheet_name (str): The name of the sheet to adjust column widths for. + """ + # Load the existing workbook + # Adjust column widths based on the maximum length of the content in each column + for col in worksheet.columns: + max_length = 0 + column = col[0].column_letter # Get the column letter + for cell in col: + if column != "A": + cell.alignment = Alignment(wrap_text=True, vertical="top") + if cell.value is not None: + cell_length = len(str(cell.value)) + if cell_length > max_length: + max_length = cell_length + if max_length > 0: # Only adjust if there are filled values in the column + max_length = max(min(max_length, 28), 11) + adjusted_width = max_length + 2 + worksheet.column_dimensions[column].width = adjusted_width + + +def shade_30_rows_and_protect_sheet(worksheet: Worksheet, startrow: int): + """For use after all data is written so there is a clear border around the data""" + for r in range(startrow, startrow + 30): + protect_and_shade_row(worksheet, r) + worksheet.protection = SheetProtection( + sheet=True, + formatCells=False, + formatColumns=False, + formatRows=False, + insertColumns=False, + insertRows=True, + insertHyperlinks=False, + deleteColumns=False, + deleteRows=True, + selectLockedCells=False, + selectUnlockedCells=False, + ) + + +def replace_row_with_multiple_rows(original_df, new_df, row_to_replace): + """ + Replace a specified row in the original DataFrame with multiple rows from the new DataFrame. + + Parameters: + original_df (pd.DataFrame): The original DataFrame. + new_df (pd.DataFrame): The new DataFrame with the rows to insert. + row_to_replace (str): The name of the row to be replaced. + + Returns: + pd.DataFrame: The updated DataFrame with the specified row replaced by the new rows. + """ + # Split the original DataFrame into two parts: before and after the row to be replaced + df_before = original_df.loc[:row_to_replace].iloc[:-1] + df_after = original_df.loc[row_to_replace:].drop(row_to_replace, axis=0) + + # Concatenate the parts with the new rows + df_replaced = pd.concat([df_before, new_df, df_after]) + # df_replaced = df_replaced.dropna(how="all", axis=1) + return df_replaced + + +def pydantic_to_dataframe( + ob: Union[BaseModel, List[BaseModel]], + debug: bool = False, +) -> Tuple[pd.DataFrame, List[int]]: + """ + Convert to a dataframe, identifying rows that are made of lists and exploding them over multiple rows with + hierarchical indices if needed. + + Returns the dataframe and also a list of the indexs (denoted by zero-based numbers) that are of list types. + The list of indexs is intended to be used for appropriately shading the excel sheet. + """ + if isinstance(ob, list): + ob_dict = [elem.model_dump() for elem in ob] + annotations = {k: v.annotation for k, v in ob[0].model_fields.items()} + is_list_of_objects = True + else: + ob_dict = ob.model_dump() + annotations = {k: v.annotation for k, v in ob.model_fields.items()} + is_list_of_objects = False + df = pd.json_normalize(ob_dict).T + if debug: + print("pydantic_to_dataframe") + print(df) + + # handle dictionaries + # for idx, field in ob_dict.items(): + # if annotation_contains_dict(annotations[idx]): + for fieldname, anno in annotations.items(): + if annotation_contains_dict(anno): + if debug: + print("Found a dictionary") + if is_list_of_objects: + continue + assert_dict_annotation_is_strings_or_any(anno) + field = ob_dict[fieldname] + if field is None or len(field) == 0: + dict_df = pd.DataFrame(["", ""], index=["key", "value"]) + else: + dict_df = pd.DataFrame([field.keys(), field.values()], index=["key", "value"]) + dict_df.index = dict_df.index.map(lambda x: f"{fieldname}.{x}") + df = df[~df.index.str.startswith(f"{fieldname}.")] + df = df[df.index != fieldname] + df = pd.concat([df, dict_df]) + + i = 0 + list_indices = [] + enums = {} + for idx in df.index: + if debug: + print(f"pydantic_to_dataframe::172 idx = {idx}, df = {df}") + vals = df.loc[idx] # [0] + if debug: + print(f"vals: {vals}") + print(f'idx.split(".")[0]: {idx.split(".")[0]}') + print(f'annotations[idx.split(".")[0]]: {annotations[idx.split(".")[0]]}') + # field = ob_dict[idx.split(".")[0]] + + if annotation_contains_list(annotations[idx.split(".")[0]]) or annotation_contains_dict( + annotations[idx.split(".")[0]] + ): + if annotation_contains_list(annotations[idx.split(".")[0]]): + subtype = get_subtype_of_optional_or_list(annotations[idx.split(".")[0]]) + else: + subtype = dict + if debug: + print("subtype = ", subtype) + print("isinstance(subtype, BaseModel)", isinstance(subtype, type(BaseModel))) + print("isinstance(subtype, dict)", isinstance(subtype, dict)) + if is_list_of_objects: + if debug: + print("list of lists") + list_indices.append(i) + i += 1 + elif isinstance(subtype, type(BaseModel)) or isinstance(subtype, dict): + if debug: + print("list of base models", vals) + sub = pd.json_normalize(df.loc[idx].values[0]).reset_index(drop=True).T + sub.index = sub.index.map(lambda x: f"{idx}." + x) + df = replace_row_with_multiple_rows(df, sub, idx) + list_indices += list(range(i, i + len(sub))) + i += len(sub) + else: + if debug: + print("list of builtins or else empty") + df = replace_row_with_multiple_rows(df, df.loc[idx].explode().to_frame().reset_index(drop=True).T, idx) + list_indices.append(i) + i += 1 + else: + if isinstance(annotations[idx.split(".")[0]], type(Enum)): + dropdown_options = [e.value for e in annotations[idx.split(".")[0]]] + dropdown = DataValidation( + type="list", + formula1=f'"{",".join(dropdown_options)}"', + showDropDown=False, + allow_blank=True, + showErrorMessage=True, + ) + enums[i] = dropdown + i += 1 + if debug: + print(df) + if len(df): + df.index = df.index.str.split(".", expand=True) + if is_list_of_objects: + list_indices = list(range(len(df))) + return df, list_indices, enums + + +def stringify_enum(elem): + if isinstance(elem, Enum): + return str(elem.value) + else: + raise TypeError(f"{elem} is not an enum") + + +def stringify_cell_element(elem): + if isinstance(elem, list): + return json.dumps(elem, default=stringify_enum) + elif isinstance(elem, Enum): + return str(elem.value) + elif isinstance(elem, dict): + return json.dumps(elem, default=stringify_enum) + else: + return elem + + +def write_pydantic_to_excel(ws, ob, row_number, debug=False): + df, list_rows, enums = pydantic_to_dataframe(ob, debug=debug) + list_rows_tracker = {} + list_of_enums_tracker = {} + for i, r in enumerate(dataframe_to_rows(df, index=True, header=False)): + if debug: + print(r) + if all(map(lambda x: x is None, r)): + continue + r = [stringify_cell_element(val) for val in r] + # r = [str(val) if isinstance(val, list) else str(val.value) if isinstance(val, Enum) else val for val in r ] + r = [""] + r + if debug: + print("about to append", r) + ws.append(r) + for col in range(2, df.index.nlevels + 2): + cell = ws.cell(row=row_number, column=col) + cell.font = Font(bold=True) + cell.border = Border( + top=Side(border_style=None), + left=Side(border_style="thin"), + right=Side(border_style="thin"), + bottom=Side(border_style=None), + ) + if cell.value is not None and cell.value != "": + if debug: + print("turning on some borders") + border_copy = copy.copy(cell.border) + border_copy.top = Side(border_style="thin") + cell.border = border_copy + min_unprotected_cell = df.index.nlevels + 2 + max_unprotected_cell = None if i - 1 in list_rows else min_unprotected_cell + unprotect_row(ws, row_number, colmin=min_unprotected_cell, colmax=max_unprotected_cell) + if i - 1 in enums: + dropdown = enums[i - 1] + ws.add_data_validation(dropdown) + for j in range( + min_unprotected_cell, ws.max_column if max_unprotected_cell is None else min_unprotected_cell + 1 + ): + dropdown.add(ws.cell(row_number, j)) + if max_unprotected_cell is None: + list_rows_tracker[row_number] = ws.max_column + if i - 1 in enums: + list_of_enums_tracker[row_number] = dropdown + row_number += 1 + + for col in range(2, df.index.nlevels + 2): + cell = ws.cell(row=row_number, column=col) + border_copy = copy.copy(cell.border) + border_copy.top = Side(border_style="thin") + cell.border = border_copy + + return row_number + 1, list_rows_tracker, list_of_enums_tracker + + +def write_title_and_version_info( + ws: Worksheet, sheet_title: Optional[str], version: Optional[str], protect_title=True +) -> int: + if sheet_title is None: + return 1 + if sheet_title is not None: + sheet_title = sheet_title.replace("_", " ") + ws.append([sheet_title, None, version]) + + if sheet_title is not None: + bold_font = Font(bold=True, size=14) + ws["A1"].font = bold_font + if protect_title == False: + unprotect_row(ws, 1, colmin=1, colmax=1) + + if version is not None: + version_font = Font(name="Consolas", size=9) + ws["C1"].font = version_font + + ws.append([]) + return 3 + + +def write_pydantic_to_sheet(worksheet: Worksheet, ob: BaseModel, current_row: int, debug: bool = False) -> int: + children = seperate_simple_from_pydantic(ob) + if debug: + print("Children:") + print(children) + list_rows = {} + enum_list_rows = {} + + if len(children["simple"]): + child_object = subset_pydantic_model(ob, children["simple"]) + current_row, sub_list_rows, sub_list_enums = write_pydantic_to_excel( + ws=worksheet, ob=child_object, row_number=current_row + ) + list_rows.update(sub_list_rows) + enum_list_rows.update(sub_list_enums) + + for mfield in children["pydantic"]: + worksheet.append([mfield]) + worksheet.cell(row=current_row, column=1).font = Font(bold=True, size=12) + current_row += 1 + child_object = getattr(ob, mfield) + current_row, sub_list_rows, sub_list_enums = write_pydantic_to_excel( + ws=worksheet, ob=child_object, row_number=current_row + ) + list_rows.update(sub_list_rows) + enum_list_rows.update(sub_list_enums) + + for row, col in list_rows.items(): + unprotect_row(worksheet, row, colmin=col, colmax=None) + if row in enum_list_rows: + dropdown = enum_list_rows[row] + for j in range(col, worksheet.max_column): + dropdown.add(worksheet.cell(row, j)) + return current_row + + +def open_or_create_workbook(doc_filepath): + if os.path.exists(doc_filepath): + workbook = load_workbook(doc_filepath) + else: + workbook = Workbook() + # Remove the default sheet created by Workbook() + if len(workbook.sheetnames) == 1 and workbook.sheetnames[0] == "Sheet": + del workbook["Sheet"] + return workbook + + +def create_sheet(workbook, sheetname, sheet_number): + # Check if the sheet already exists + if sheetname in workbook.sheetnames: + raise ValueError(f"A sheet called '{sheetname}' already exists in the document.") + + # Create a new sheet + new_sheet = workbook.create_sheet(title=sheetname) + + # Determine the position to insert the new sheet + total_sheets = len(workbook.sheetnames) + insert_position = min(sheet_number, total_sheets) + + # Move the new sheet to the specified position + workbook._sheets.insert(insert_position, workbook._sheets.pop()) + return new_sheet + + +def write_to_single_sheet( + doc_filepath: str, ob: BaseModel, metadata_type: str, title: Optional[str] = None, verbose=False +): + model_default_name = ob.model_json_schema()["title"] + if title is None: + title = model_default_name + wb = open_or_create_workbook(doc_filepath) + ws = create_sheet(wb, "metadata", sheet_number=0) + version = f"{metadata_type} type metadata version 20240812.1" + current_row = write_title_and_version_info(ws, title, version, protect_title=False) + current_row = write_pydantic_to_sheet(ws, ob, current_row, debug=verbose) + correct_column_widths(worksheet=ws) + shade_30_rows_and_protect_sheet(worksheet=ws, startrow=current_row) + shade_locked_cells(worksheet=ws) + wb.save(doc_filepath) + + +def write_across_many_sheets( + doc_filepath: str, ob: BaseModel, metadata_type: str, title: Optional[str] = None, verbose=False +): + wb = open_or_create_workbook(doc_filepath) + ws = create_sheet(wb, "metadata", sheet_number=0) + version = f"{metadata_type} type metadata version 20240905.1" + current_row = write_title_and_version_info(ws, title, version, protect_title=False) + + children = seperate_simple_from_pydantic(ob) + if verbose: + print(f"children: {children}") + sheet_number = 0 + + if len(children["simple"]): + child_object = subset_pydantic_model(ob, children["simple"]) + + current_row = write_pydantic_to_sheet(ws, child_object, current_row, debug=verbose) + correct_column_widths(worksheet=ws) + shade_30_rows_and_protect_sheet(worksheet=ws, startrow=current_row) + shade_locked_cells(worksheet=ws) + sheet_number += 1 + + for fieldname in children["pydantic"]: + if verbose: + print(f"\n\n{fieldname}\n") + child_object = getattr(ob, fieldname) + if verbose: + print(child_object) + ws = create_sheet(wb, fieldname, sheet_number=sheet_number) + if not isinstance(child_object, BaseModel): + child_object = subset_pydantic_model(ob, [fieldname], name=fieldname) + sheet_title = None + else: + sheet_title = fieldname + current_row = write_title_and_version_info(ws, sheet_title, None, protect_title=True) + current_row = write_pydantic_to_sheet(ws, child_object, current_row, debug=verbose) + correct_column_widths(worksheet=ws) + shade_30_rows_and_protect_sheet(worksheet=ws, startrow=current_row) + shade_locked_cells(worksheet=ws) + sheet_number += 1 + wb.save(doc_filepath) diff --git a/pydantic_schemas/utils/quick_start.py b/pydantic_schemas/utils/quick_start.py new file mode 100644 index 0000000..13d11bd --- /dev/null +++ b/pydantic_schemas/utils/quick_start.py @@ -0,0 +1,235 @@ +import importlib +import inspect +import typing +from enum import Enum +from typing import Any, Callable, Dict, List, Type + +from pydantic import AnyUrl, BaseModel + +from .utils import standardize_keys_in_dict + +DEFAULT_URL = "http://www.example.com" + + +def _is_typing_annotation(annotation): + if isinstance(annotation, str): + return False # Skip forward references which are in string form + + # Check if the annotation is directly from typing + if getattr(annotation, "__module__", None) == "typing": + return True + + # Handle special cases for generic types like List[int], Dict[str, int], etc. + origin = getattr(annotation, "__origin__", None) + if origin and getattr(origin, "__module__", None) == "typing": + return True + + return False + + +def _is_builtin_type(tp): + return tp in (str, int, float, bool, bytes, complex) + + +def _is_enum_type(tp): + return inspect.isclass(tp) and issubclass(tp, Enum) + + +def _is_pydantic_subclass(cl): + return inspect.isclass(cl) and issubclass(cl, BaseModel) + + +def _filter_list_for_condition(args: List[Any], condition: Callable[[Any], bool]) -> List[Any]: + return [a for a in args if condition(a)] + + +def _is_pydantic_annotated_string(p, debug=False, indentation=""): + if typing.get_origin(p) is typing.Annotated: + args = typing.get_args(p) + if args[0] is str: + if debug: + print(indentation, "Is Annotated String") + return True + if debug: + print(indentation, f"Is Annotated but not a string {p}") + return False + + +def _is_pydantic_annotated_float(p, debug=False, indentation=""): + if typing.get_origin(p) is typing.Annotated: + args = typing.get_args(p) + if args[0] is float: + if debug: + print(indentation, "Is Annotated float") + return True + if debug: + print(indentation, f"Is Annotated but not a float {p}") + return False + + +def _create_default_class_from_annotation( + p: Any, is_optional: bool = False, debug: bool = False, indentation: str = "" +): + if p is str: + if debug: + print(indentation, "STR") + if is_optional: + return None + else: + return "" + elif p is float: + if debug: + print(indentation, "STR") + if is_optional: + return None + else: + raise ValueError("Cannot create default float as it's not optional") + elif _is_enum_type(p): + if debug: + print(indentation, "ENUM") + if is_optional: + return None + else: + return list(p)[0].value # get first value of the enum + elif _is_pydantic_subclass(p): + if debug: + print(indentation, "pydantic CLASS") + return make_skeleton(p, debug=debug, indentation=indentation + " ") + elif isinstance(p, type(AnyUrl)): + return DEFAULT_URL + else: + raise ValueError(f"Unknown annotation: {p}") + + +def _create_default_from_list_of_args(args: List[Any], is_optional=True, debug=False, indentation=""): + """ + return None for built in types and enums, but create skeletons of pydantic or typed parameters + """ + args = _filter_list_for_condition(args, lambda a: a is not type(None)) + typed_args = _filter_list_for_condition(args, _is_typing_annotation) # _filter_list_for_typing_args(args) + pydantic_args = _filter_list_for_condition(args, _is_pydantic_subclass) # _filter_for_pydantic_args(args) + if debug: + print( + indentation, + f"LIST OF ARGS: {args}, LIST OF TYPED ARGS: {typed_args}, LIST_OF_PYDANTIC_ARGS: {pydantic_args}", + ) + if len(typed_args): + if debug: + print(indentation, "moving to _create_default_from_typing_annotation") + # because dicts are more complicated than lists, we should default to dicts + typed_dicts = _filter_list_for_condition(typed_args, lambda p: getattr(p, "__origin__", None) is dict) + typed_lists = _filter_list_for_condition(typed_args, lambda p: getattr(p, "__origin__", None) is list) + if len(typed_dicts): + chosen_type = typed_dicts[0] + elif len(typed_lists): + chosen_type = typed_lists[0] + else: + chosen_type = typed_args[0] + return _create_default_from_typing_annotation( + chosen_type, is_optional=is_optional, debug=debug, indentation=indentation + ) + elif len(pydantic_args): + return make_skeleton(pydantic_args[0], debug=debug, indentation=indentation + " ") + elif len(_filter_list_for_condition(args, lambda a: _is_builtin_type(a) or _is_enum_type(a))): + if debug: + print(indentation, "all builtins or enums") + if is_optional: + return None + elif len(_filter_list_for_condition(args, lambda a: a is str)): + return "" + else: + raise ValueError(f"Can't create a default of {args}") + elif len(args) == 1 and _is_pydantic_annotated_string(args[0], debug=debug, indentation=indentation): + if is_optional: + return None + else: + return "" + elif len(args) == 1 and _is_pydantic_annotated_float(args[0], debug=debug, indentation=indentation): + if is_optional: + return None + else: + raise ValueError(f"Can't create a default of {args}") + elif len(args) == 1 and isinstance(args[0], type(AnyUrl)): + if is_optional: + return None + else: + return DEFAULT_URL + else: + raise ValueError(f"Can't create a default of {args}") + + +def _create_default_from_typing_annotation(p: Any, is_optional: bool = False, debug: bool = False, indentation=""): + if debug: + print(indentation, "_create_default_from_typing_annotation") + if p is typing.Any: + return "" + args = typing.get_args(p) + if len(args) == 0: + raise ValueError(p) + isOptional = type(None) in args + if isOptional: + if debug: + print(indentation, "isOPTIONAL") + return _create_default_from_list_of_args(args, is_optional=True, debug=debug, indentation=indentation) + elif getattr(p, "__origin__", None) is list: + if debug: + print(indentation, "isLIST") + if _is_pydantic_subclass(args[0]): + return [make_skeleton(args[0], debug=debug, indentation=indentation + " ")] + else: + if is_optional: + return [] + else: + return [_create_default(args[0], is_optional=False, debug=debug, indentation=indentation + " ")] + elif getattr(p, "__origin__", None) is dict: + if debug: + print(indentation, "isDICT") + k = _create_default(args[0], debug=debug, indentation=indentation + " ") + v = _create_default(args[1], debug=debug, indentation=indentation + " ") + return {k: v} + elif len(args) > 1: + if debug: + print(indentation, "isUNION") + return _create_default_from_list_of_args(args, is_optional=is_optional, debug=debug, indentation=indentation) + else: + raise ValueError(f"Unknown typing {p}") + + +def _create_default(p: inspect.Parameter, is_optional: bool = False, debug: bool = False, indentation: str = ""): + if hasattr(p, "annotation"): + p = p.annotation + if inspect.isclass(p) and not _is_typing_annotation(p): + if debug: + print(indentation, "CLASS") + return _create_default_class_from_annotation(p, is_optional=is_optional, debug=debug, indentation=indentation) + elif _is_typing_annotation(p): + if debug: + print(indentation, "TYPED") + return _create_default_from_typing_annotation(p, is_optional=is_optional, debug=debug, indentation=indentation) + elif _is_pydantic_annotated_string(p, debug=debug, indentation=indentation): + if debug: + print(indentation, "ANNOTATED STRING") + if is_optional: + return None + else: + return "" + else: + raise ValueError(f"Unknown parameter {p}") + + +def make_skeleton(cl: Type[BaseModel], debug=False, indentation=""): + parameter_map = inspect.signature(cl).parameters # {'name': } + param_values = {} + for name, param in parameter_map.items(): + if debug: + print(indentation, f"{param.name}: {param.annotation}") + param_values[name] = _create_default(param, debug=debug, indentation=indentation + " ") + if debug: + print(indentation, f"Parameter: {name}, value: {param_values[name]}") + param_values = standardize_keys_in_dict(param_values) + return cl(**param_values) + + +# def create_empty_schema_from_path(module_name, class_name, debug=False): +# MyClass = getattr(importlib.import_module(module_name), class_name) +# return make_skeleton(MyClass, debug=debug) diff --git a/pydantic_schemas/utils/schema_base_model.py b/pydantic_schemas/utils/schema_base_model.py new file mode 100644 index 0000000..90d8df9 --- /dev/null +++ b/pydantic_schemas/utils/schema_base_model.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel, ConfigDict + + +class SchemaBaseModel(BaseModel): + model_config = ConfigDict( + validate_assignment=True, protected_namespaces=(), use_enum_values=True, extra="forbid" + ) # if a subclass has a model_config then this will be overridden + + def __setitem__(self, key, value): + """Allow dict like setting: Model[key] = value""" + setattr(self, key, value) diff --git a/pydantic_schemas/utils/utils.py b/pydantic_schemas/utils/utils.py new file mode 100644 index 0000000..a7b3d02 --- /dev/null +++ b/pydantic_schemas/utils/utils.py @@ -0,0 +1,249 @@ +import re +import typing +from typing import Any, Callable, Dict, List, Optional, Type, Union + +from pydantic import BaseModel, create_model + + +def is_optional_annotation(anno: typing._UnionGenericAlias) -> bool: + return type(None) in typing.get_args(anno) + + +def is_union_annotation(anno: typing._UnionGenericAlias) -> bool: + # return len(typing.get_args(anno))>=2 + origin = typing.get_origin(anno) + return origin in [Optional, Union] + + +def is_dict_annotation(anno: typing._UnionGenericAlias) -> bool: + return typing.get_origin(anno) is dict + + +def is_list_annotation(anno: typing._UnionGenericAlias) -> bool: + return typing.get_origin(anno) is list + + +def is_optional_list(anno: typing._UnionGenericAlias) -> bool: + if is_optional_annotation(anno): + args = typing.get_args(anno) + if len(args) == 1 and is_list_annotation(args[0]): + return True + return False + + +def get_subtype_of_optional_or_list(anno: typing._UnionGenericAlias, debug=False) -> Any: + if debug: + print(f"getting subtype of {anno}") + args = typing.get_args(anno) + if debug: + for a in args: + print(f"getting subtype {a}, is it NoneType?={a is type(None)}") + args = [a for a in args if not a is type(None)] + for arg in args: + if hasattr(arg, "annotation") and is_dict_annotation(arg.annotation): + raise NotImplementedError("DICTS not yet implemented") + for arg in args: + if debug: + print(f"checking arg {arg} -- {hasattr(arg, 'annotation')} -- {is_list_annotation(arg)}") + if hasattr(arg, "annotation") and is_list_annotation(arg.annotation): + return get_subtype_of_optional_or_list(arg.annotation, debug=debug) + elif is_list_annotation(arg): + return get_subtype_of_optional_or_list(arg, debug=debug) + if len(args) == 1: + return args[0] + else: + raise NotImplementedError("Only optional lists optional builtin types implemented") + + +def _annotation_contains_generic( + anno: typing._UnionGenericAlias, checker: Callable[[typing._UnionGenericAlias], bool] +) -> bool: + if checker(anno): + return True + if is_union_annotation(anno): + args = typing.get_args(anno) + args = [a for a in args if not a is type(None)] + for a in args: + if checker(a): + return True + if is_optional_annotation(anno) or is_list_annotation(anno): # optional check is pointless given union check above + subtype = get_subtype_of_optional_or_list(anno) + return checker(subtype) + return False + + +def annotation_contains_list(anno: typing._UnionGenericAlias) -> bool: + return _annotation_contains_generic(anno, is_list_annotation) + + +def annotation_contains_dict(anno: typing._UnionGenericAlias) -> bool: + return _annotation_contains_generic(anno, is_dict_annotation) + + +def annotation_contains_pydantic(anno: typing._UnionGenericAlias) -> bool: + return _annotation_contains_generic(anno, lambda x: isinstance(x, type(BaseModel))) + + +def assert_dict_annotation_is_strings_or_any(anno): + if is_dict_annotation(anno): + args = typing.get_args(anno) + for a in args: + if not (a is str or a is typing.Any): + raise AssertionError(f"exepcted dictionaries of strings to strings or Any but got {anno}") + elif is_optional_annotation(anno): + assert_dict_annotation_is_strings_or_any(get_subtype_of_optional_or_list(anno)) + else: + raise ValueError(f"Expected dictionary or optional dictionary annotation but got {anno}") + + +def seperate_simple_from_pydantic(ob: BaseModel) -> Dict[str, Dict]: + """ + Returns a dictionary of lists of field names that are either of other pydantic types or of other types + """ + simple_children = [] + pydantic_children = [] + for mfield, field_info in ob.model_fields.items(): + if annotation_contains_pydantic(field_info.annotation): + pydantic_children.append(mfield) + else: + simple_children.append(mfield) + return {"simple": simple_children, "pydantic": pydantic_children} + + +def merge_dicts(base, update): + """merge a pair of dicitonaries in which the values are themselves either dictionaries to be merged or lists of + dictionaries to be merged""" + if len(update) == 0: + return base + elif len(base) == 0: + return update + new_dict = {} + for key, base_value in base.items(): + if key in update: + update_value = update[key] + if isinstance(base_value, dict): + if isinstance(update_value, dict): + new_dict[key] = merge_dicts(base_value, update_value) + else: + new_dict[key] = base_value + elif isinstance(base_value, list): + if isinstance(update_value, list) and len(update_value) > 0: + new_list = [] + min_length = min(len(base_value), len(update_value)) + for i in range(min_length): + if isinstance(base_value[i], dict): + if isinstance(update_value[i], dict): + new_list.append(merge_dicts(base_value[i], update_value[i])) + else: + new_list.append(base_value[i]) + else: + new_list.append(update_value[i]) + new_list.extend(update_value[min_length:]) + new_dict[key] = new_list + else: + new_dict[key] = base_value + else: + if update_value is not None: + new_dict[key] = update_value + else: + new_dict[key] = base_value + else: + new_dict[key] = base_value + for key, update_value in update.items(): + if key not in base: + new_dict[key] = update_value + return new_dict + + +def capitalize_first_letter(s): + if s: + return s[0].upper() + s[1:] + return s + + +def split_on_capitals(s): + # Use regular expression to split on capitalized letters + return re.findall(r"[a-z]+|[A-Z][a-z]*", s) + + +def _standardize_keys_in_list_of_possible_dicts(lst: List[any], snake_to_pascal, pascal_to_snake) -> List[Any]: + new_value = [] + for item in lst: + if isinstance(item, dict): + new_value.append( + standardize_keys_in_dict(item, snake_to_pascal=snake_to_pascal, pascal_to_snake=pascal_to_snake) + ) + elif isinstance(item, list): + new_value.append( + _standardize_keys_in_list_of_possible_dicts( + item, snake_to_pascal=snake_to_pascal, pascal_to_snake=pascal_to_snake + ) + ) + else: + new_value.append(item) + return new_value + + +def standardize_keys_in_dict( + d: Dict[str, Any], snake_to_pascal: bool = False, pascal_to_snake: bool = False +) -> Dict[str, Any]: + """ + sometimes when field names are also python protected names like 'from' and 'import' + then we append an underscore to the field name to avoide clashes. + + But pydantic doesn't expect that underscore to be there when instantiating, so we must remove it. + """ + new_dict = {} + for key, value in d.items(): + new_key = key.replace(" ", "_").rstrip("_") + new_key = new_key.split(".")[-1] + if snake_to_pascal: + new_key = "".join([capitalize_first_letter(x) for x in new_key.split("_")]) + elif pascal_to_snake: + new_key = "_".join([x.lower() for x in split_on_capitals(new_key)]) + if isinstance(value, dict): + new_value = standardize_keys_in_dict( + value, snake_to_pascal=snake_to_pascal, pascal_to_snake=pascal_to_snake + ) + elif isinstance(value, list): + new_value = _standardize_keys_in_list_of_possible_dicts( + value, snake_to_pascal, pascal_to_snake=pascal_to_snake + ) + else: + new_value = value + new_dict[new_key] = new_value + return new_dict + + +def subset_pydantic_model_type( + model_type: Type[BaseModel], feature_names: List[str], name: Optional[str] = None +) -> Type[BaseModel]: + """ + Create a new Pydantic model type with only the specified subset of features. + + :param model: The original Pydantic model object. + :param feature_names: List of feature names to include in the new model. + :return: A new Pydantic model type with the specified features from the original model + """ + # Filter the fields of the original model based on the feature names + fields = { + name: (model_type.model_fields[name].annotation, model_type.model_fields[name].default) + for name in feature_names + if name in model_type.model_fields + } + + # Create a new Pydantic model with the filtered fields + if name is None: + name = "SubsetModel" + SubModel = create_model(name, **fields) + return SubModel + + +def subset_pydantic_model(model: BaseModel, feature_names: List[str], name: Optional[str] = None) -> BaseModel: + SubModel = subset_pydantic_model_type(type(model), feature_names, name=name) + input_dict = {k: v for k, v in model.model_dump(mode="json").items() if k in feature_names} + input_dict_standardized = standardize_keys_in_dict(input_dict) + try: + return SubModel(**input_dict_standardized) + except: + raise ValueError(input_dict_standardized) diff --git a/pydantic_schemas/video_schema.py b/pydantic_schemas/video_schema.py new file mode 100644 index 0000000..3b376ab --- /dev/null +++ b/pydantic_schemas/video_schema.py @@ -0,0 +1,337 @@ +# generated by datamodel-codegen: +# filename: video-schema.json +# timestamp: 2024-09-13T19:00:33+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import Extra, Field + +from .utils.schema_base_model import SchemaBaseModel + + +class Overwrite(Enum): + """ + Overwrite document if already exists? + """ + + yes = "yes" + no = "no" + + +class Producer(SchemaBaseModel): + name: Optional[str] = Field(None, description="Name (required)", title="Name") + abbr: Optional[str] = Field(None, title="Abbreviation") + affiliation: Optional[str] = Field(None, title="Affiliation") + role: Optional[str] = Field(None, title="Role") + + +class MetadataInformation(SchemaBaseModel): + """ + Document description + """ + + class Config: + extra = Extra.forbid + + title: Optional[str] = Field(None, description="Document title", title="Document title") + idno: Optional[str] = Field(None, title="Unique ID number for the document") + producers: Optional[List[Producer]] = Field(None, description="List of producers", title="Producers") + production_date: Optional[str] = Field( + None, description="Document production date using format(YYYY-MM-DD)", title="Date of Production" + ) + version: Optional[str] = Field( + None, description="Identify and describe the current version of the document", title="Document version" + ) + + +class Identifier(SchemaBaseModel): + type: Optional[str] = Field( + None, description="Type of identifier e.g. `doi`, `handle`, `other`", title="Identifier type" + ) + identifier: str = Field(..., title="Identifier") + + +class Keyword(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + vocabulary: Optional[str] = Field(None, title="Vocabulary name") + uri: Optional[str] = Field(None, title="Vocabulary URI") + + +class Topic(SchemaBaseModel): + id: Optional[str] = Field(None, title="Unique Identifier") + name: str = Field(..., title="Topic") + parent_id: Optional[str] = Field( + None, description="For subtopics, provide the ID of the parent topic", title="Parent topic Identifier" + ) + vocabulary: Optional[str] = Field( + None, description="Name of the controlled vocabulary, if the topic is from a taxonomy.", title="Vocabulary" + ) + uri: Optional[str] = Field( + None, + description="Link to the controlled vocabulary web page, if the topic is from a taxonomy.", + title="Vocabulary URI", + ) + + +class Person(SchemaBaseModel): + name: str = Field(..., title="Name") + role: Optional[str] = Field(None, title="Role") + + +class CountryItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Country name") + code: Optional[str] = Field(None, title="Country code") + + +class BboxItem(SchemaBaseModel): + west: Optional[str] = Field(None, title="West") + east: Optional[str] = Field(None, title="East") + south: Optional[str] = Field(None, title="South") + north: Optional[str] = Field(None, title="North") + + +class LanguageItem(SchemaBaseModel): + name: Optional[str] = Field(None, description="Language name", title="Name") + code: Optional[str] = Field(None, title="code") + + +class Contact(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name") + role: Optional[str] = Field(None, title="Role") + affiliation: Optional[str] = Field(None, title="Affiliation") + email: Optional[str] = Field(None, title="Email") + telephone: Optional[str] = Field(None, title="Telephone") + uri: Optional[str] = Field(None, title="URI") + + +class Contributor(SchemaBaseModel): + name: str = Field(..., title="Name") + affiliation: Optional[str] = Field(None, title="Affiliation") + abbreviation: Optional[str] = Field(None, title="Abbreviation") + role: Optional[str] = Field(None, title="Role") + uri: Optional[str] = Field(None, title="URI") + + +class Sponsor(SchemaBaseModel): + name: str = Field(..., title="Funding Agency/Sponsor") + abbr: Optional[str] = Field(None, title="Abbreviation") + grant: Optional[str] = Field(None, title="Grant Number") + role: Optional[str] = Field(None, title="Role") + + +class Translator(SchemaBaseModel): + first_name: Optional[str] = Field(None, title="First name") + initial: Optional[str] = Field(None, title="Initial") + last_name: Optional[str] = Field(None, title="Last name") + affiliation: Optional[str] = Field(None, title="Affiliation") + + +class TranscriptItem(SchemaBaseModel): + language: Optional[str] = Field(None, title="Language") + text: Optional[str] = Field(None, title="Text") + + +class AlbumItem(SchemaBaseModel): + name: Optional[str] = Field(None, title="Name of album") + description: Optional[str] = Field(None, title="Description") + owner: Optional[str] = Field(None, title="Owner") + uri: Optional[str] = Field(None, title="URI") + + +class VideoDescription(SchemaBaseModel): + """ + Video description + """ + + idno: str = Field(..., title="Unique video identifier") + identifiers: Optional[List[Identifier]] = Field(None, description="Other identifiers", title="Other identifiers") + title: str = Field(..., description="Title") + alt_title: Optional[str] = Field(None, description="Alternate title or other title") + description: Optional[str] = Field(None, description="Description") + genre: Optional[str] = Field(None, description="Genre") + keywords: Optional[List[Keyword]] = None + topics: Optional[List[Topic]] = Field( + None, + description="Topics covered by the table (ideally, the list of topics will be a controlled vocabulary)", + title="Topics", + ) + persons: Optional[List[Person]] = Field(None, title="Persons shown in the video") + main_entity: Optional[str] = Field(None, description="Primary entity described in the video") + date_created: Optional[str] = Field(None, description="Date of creation (YYYY-MM-DD)") + date_published: Optional[str] = Field(None, description="Date published (YYYY-MM-DD)") + version: Optional[str] = Field(None, description="Version") + status: Optional[str] = Field( + None, + description=( + "Status of a creative work in terms of its stage in lifecycle. e.g. `incomplete`, `draft`, `published`," + " `obsolete`" + ), + title="Creative work status", + ) + country: Optional[List[CountryItem]] = Field(None, title="Countries") + spatial_coverage: Optional[str] = Field(None, description="Place(s) which are the focus of the content") + content_reference_time: Optional[str] = Field( + None, + description=( + "Specific time described by a creative work, for works that emphasize a particular moment within an Event" + ), + ) + temporal_coverage: Optional[str] = Field( + None, description="Period that the content applies to using ISO 8601 date time format" + ) + recorded_at: Optional[str] = Field(None, description="Location where video was recorded") + audience: Optional[str] = Field(None, description="Intended audience") + bbox: Optional[List[BboxItem]] = Field(None, title="Geographic bounding box") + language: Optional[List[LanguageItem]] = Field(None, description="languages") + creator: Optional[str] = Field(None, description="Creator") + production_company: Optional[str] = Field(None, description="Production company") + publisher: Optional[str] = Field(None, description="Publisher") + repository: Optional[str] = Field(None, title="Repository") + contacts: Optional[List[Contact]] = Field(None, description="Contacts", title="Contacts") + contributors: Optional[List[Contributor]] = None + sponsors: Optional[List[Sponsor]] = Field(None, title="Funding Agency/Sponsor") + translators: Optional[List[Translator]] = Field(None, description="Translators", title="Translators") + is_based_on: Optional[str] = Field( + None, + description="A resource from which this work is derived or from which it is a modification or adaption", + title="A resource from which this work is derived", + ) + is_part_of: Optional[str] = Field(None, title="Indicate an item that this item is part of") + relations: Optional[List[str]] = Field( + None, + title=( + "Defines, as a free text field, the relation between the video being documented and other resources. This" + " is a Dublin Core element." + ), + ) + video_provider: Optional[str] = Field(None, description="Video provider e.g. youtube, vimeo, facebook") + video_url: Optional[str] = Field(None, description="Video URL") + embed_url: Optional[str] = Field(None, description="Video embed URL") + encoding_format: Optional[str] = Field(None, description="Media type using a MIME format", title="Encoding format") + duration: Optional[str] = Field( + None, description="The duration of the video in ISO 8601 date time format - `hh:mm:ss`", title="Duration" + ) + rights: Optional[str] = Field(None, description="Rights") + copyright_holder: Optional[str] = Field( + None, description="The party holding the legal copyright", title="Copyright holder" + ) + copyright_notice: Optional[str] = Field( + None, description="Text of a notice describing the copyright", title="Copyright text" + ) + copyright_year: Optional[str] = Field( + None, description="Year during which claimed copyright for the video was first asserted", title="Copyright year" + ) + credit_text: Optional[str] = Field( + None, + description=( + "This element that can be used to credit the person(s) and/or organization(s) associated with a published" + " video. It corresponds to the `creditText` element of VideoObject." + ), + title="Credits", + ) + citation: Optional[str] = Field( + None, + description="This element provides a required or recommended citation of the audio file.", + title="Citation", + ) + transcript: Optional[List[TranscriptItem]] = Field(None, title="Transcript") + media: Optional[List[str]] = Field(None, title="Media") + album: Optional[List[AlbumItem]] = Field(None, title="Album") + + +class Tag(SchemaBaseModel): + tag: Optional[str] = Field(None, title="Tag") + tag_group: Optional[str] = Field(None, title="Tag group") + + +class ModelInfoItem(SchemaBaseModel): + source: Optional[str] = Field(None, title="Source") + author: Optional[str] = Field(None, title="Author") + version: Optional[str] = Field(None, title="Version") + model_id: Optional[str] = Field(None, title="Model Identifier") + nb_topics: Optional[float] = Field(None, title="Number of topics") + description: Optional[str] = Field(None, title="Description") + corpus: Optional[str] = Field(None, title="Corpus name") + uri: Optional[str] = Field(None, title="URI") + + +class TopicWord(SchemaBaseModel): + word: Optional[str] = Field(None, title="Word") + word_weight: Optional[float] = Field(None, title="Word weight") + + +class TopicDescriptionItem(SchemaBaseModel): + topic_id: Optional[Union[int, str]] = Field(None, title="Topic identifier") + topic_score: Optional[Union[float, str]] = Field(None, title="Topic score") + topic_label: Optional[str] = Field(None, title="Topic label") + topic_words: Optional[List[TopicWord]] = Field(None, description="Words", title="Topic words") + + +class LdaTopic(SchemaBaseModel): + class Config: + extra = Extra.forbid + + model_info: Optional[List[ModelInfoItem]] = Field(None, title="Model information") + topic_description: Optional[List[TopicDescriptionItem]] = Field(None, title="Topic information") + + +class Embedding(SchemaBaseModel): + id: str = Field(..., title="Vector Model ID") + description: Optional[str] = Field(None, title="Vector Model Description") + date: Optional[str] = Field(None, title="Date (YYYY-MM-DD)") + vector: Dict[str, Any] = Field(..., title="Vector") + + +class OriginDescription(SchemaBaseModel): + harvest_date: Optional[str] = Field(None, description="Harvest date using UTC date format") + altered: Optional[bool] = Field( + None, description="If the metadata was altered before dissemination", title="Metadata altered" + ) + base_url: Optional[str] = Field(None, description="Base URL of the originating repository") + identifier: Optional[str] = Field(None, description="Unique idenifiter of the item from the originating repository") + date_stamp: Optional[str] = Field( + None, + description="Datestamp (UTC date format) of the metadata record disseminated by the originating repository", + ) + metadata_namespace: Optional[str] = Field( + None, + description=( + "Metadata namespace URI of the metadata format of the record harvested from the originating repository" + ), + ) + + +class ProvenanceSchema(SchemaBaseModel): + """ + Provenance of metadata based on the OAI provenance schema (http://www.openarchives.org/OAI/2.0/provenance.xsd) + """ + + origin_description: Optional[OriginDescription] = Field(None, title="Origin description") + + +class Model(SchemaBaseModel): + """ + Video schema based on the elements from Dublin Core and Schema.org's VideoObject + """ + + repositoryid: Optional[str] = Field( + None, + description="Abbreviation for the collection that owns the document", + title="Collection ID that owns the document", + ) + published: Optional[int] = Field(0, description="Status - 0=draft, 1=published", title="Status") + overwrite: Optional[Overwrite] = Field("no", description="Overwrite document if already exists?") + metadata_information: Optional[MetadataInformation] = Field( + None, description="Document description", title="Document metadata information" + ) + video_description: VideoDescription = Field( + ..., description="Video description", title="Video metadata information" + ) + provenance: Optional[List[ProvenanceSchema]] = Field(None, description="Provenance") + tags: Optional[List[Tag]] = Field(None, description="Tags", title="Tags") + lda_topics: Optional[List[LdaTopic]] = Field(None, description="LDA topics", title="LDA topics") + embeddings: Optional[List[Embedding]] = Field(None, description="Word embeddings", title="Word embeddings") + additional: Optional[Dict[str, Any]] = Field(None, description="Additional metadata") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..09ca40f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[tool.poetry] +name = "metadataschemas" +version = "0.1.0" +description = "" +authors = ["Mehmood Asghar ", "Gordon Blackadder "] +readme = "README.md" +packages = [ + { include = "*_schema.py", from = "pydantic_schemas", to = "metadataschemas"}, + { include = "metadata_manager.py", from = "pydantic_schemas", to = "metadataschemas"}, + { include = "utils", from = "pydantic_schemas", to = "metadataschemas"}, +] + +[tool.poetry.dependencies] +python = "^3.11" +pandas = "^2.2.2" +numpy = "^2.1.0" +pydantic = "^2.8.0" +openpyxl = "^3.1.5" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.2.2" +pre-commit = "^3.7.1" +isort = "^5.13.2" +ruff = "^0.5.0" +black = "^24.4.2" +detect-secrets = "^1.5.0" +ipykernel = "^6.29.5" +datamodel-code-generator = "^0.25.9" + +[tool.ruff] +line-length = 120 +fix = true +exclude = [ + "pydantic_schemas/**" +] + +[tool.isort] +line_length = 120 +profile = "black" + +[tool.black] +line-length = 120 + +[tool.detect-secrets] +exclude-lines = "\\s*\"image/png\": \".+\"" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/schemas/document-schema.json b/schemas/document-schema.json index 1493a5f..e90581c 100644 --- a/schemas/document-schema.json +++ b/schemas/document-schema.json @@ -550,6 +550,9 @@ "string", "array" ], + "items": { + "type": "string" + }, "title": "URL", "description": "URL of the document, preferably a permanent URL" }, diff --git a/schemas/geospatial-schema.json b/schemas/geospatial-schema.json index efd194f..5f3082f 100644 --- a/schemas/geospatial-schema.json +++ b/schemas/geospatial-schema.json @@ -942,7 +942,7 @@ "referenceSystemInfo": { "type": "array", "title": "Resource Reference Systems", - "description": "Resource’s spatial reference systems - Description of the spatial and/or temporal reference systems used in the dataset.", + "description": "Resource's spatial reference systems - Description of the spatial and/or temporal reference systems used in the dataset.", "items": { "$ref": "#/definitions/referenceSystem" } @@ -982,7 +982,10 @@ "title": "Status", "description": "Status of the dataset resource. Recommended code following the [ISO/TS 19139](http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_ProgressCode) Progress codelist. Suggested values: {`completed`, `historicalArchive`, `obsolete`, `onGoing`, `planned`, `required`, `underDevelopment`, `final`, `pending`, `retired`, `superseded`, `tentative`, `valid`, `accepted`, `notAccepted`, `withdrawn`, `proposed`, `deprecated`}", "type": "array", - "_xpath": "gmd:identificationInfo/gmd:MD_DataIdentification/gmd:status/gmd:MD_ProgressCode" + "_xpath": "gmd:identificationInfo/gmd:MD_DataIdentification/gmd:status/gmd:MD_ProgressCode", + "items": { + "type": "string" + } }, "pointOfContact": { "title": "Points of contact", diff --git a/schemas/iptc-pmd-schema.json b/schemas/iptc-pmd-schema.json index 279badf..3fc3f37 100644 --- a/schemas/iptc-pmd-schema.json +++ b/schemas/iptc-pmd-schema.json @@ -284,8 +284,7 @@ "description": "Age of the human model(s) at the time this image was taken in a model released image.", "type": "array", "items": { - "type": "number", - "format": "integer" + "type": "integer" } }, "additionalModelInfo": { @@ -340,14 +339,12 @@ "maxAvailHeight": { "title": "Max Avail Height", "description": "The maximum available height in pixels of the original photo from which this photo has been derived by downsizing.", - "type": "number", - "format": "integer" + "type": "integer" }, "maxAvailWidth": { "title": "Max Avail Width", "description": "The maximum available width in pixels of the original photo from which this photo has been derived by downsizing.", - "type": "number", - "format": "integer" + "type": "integer" }, "propertyReleaseStatus": { "$ref": "iptc-phovidmdshared-schema.json#/definitions/CvTerm", diff --git a/schemas/video-schema.json b/schemas/video-schema.json index 4224330..4fd3aba 100644 --- a/schemas/video-schema.json +++ b/schemas/video-schema.json @@ -478,7 +478,10 @@ "relations": { "type": "array", - "title":"Defines, as a free text field, the relation between the video being documented and other resources. This is a Dublin Core element." + "title":"Defines, as a free text field, the relation between the video being documented and other resources. This is a Dublin Core element.", + "items": { + "type": "string" + } }, "video_provider": {