Repository: eseglem/pycql2 (language: python)

Issue:
Adds support for alternate syntax for MULTIPOINT WKT without internal parentheses around coordinates.
- Adds support for alternate syntax for MULTIPOINT WKT without internal parentheses around coordinates.
- Add alternate test for filter55


Discussion / hints:

Fix patch:
diff --git a/.editorconfig b/.editorconfig
index 3ce15f6..b06ce13 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,12 +1,10 @@
 [*]
-end_of_line = lf
-insert_final_newline = true
-
-[*.py]
 charset = utf-8
+insert_final_newline = true
+end_of_line = lf
 indent_style = space
+trim_trailing_whitespace = true
 indent_size = 4
 
-[*.json]
-indent_style = space
+[*.json,*.yaml,*.yml]
 indent_size = 2
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 0000000..069f190
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,51 @@
+name: Release
+
+on:
+  workflow_dispatch:
+
+jobs:
+  run-tests:
+    uses: ./.github/workflows/tests.yaml
+
+  release:
+    runs-on: ubuntu-latest
+    environment: release
+    permissions:
+      contents: write
+      id-token: write
+
+    steps:
+      - name: Checkout the repository
+        uses: actions/checkout@v4
+
+      - name: Install poetry
+        run: pipx install poetry
+
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+          cache: poetry
+
+      - name: Install dependencies
+        run: poetry install --no-dev
+
+      - name: Get release version
+        id: release-version
+        run: echo "RELEASE_VERSION=v$(poetry version --short)" >> "$GITHUB_OUTPUT"
+
+      - name: Build package
+        run: poetry build
+
+      - name: Upload the files to the release
+        uses: ncipollo/release-action@v1
+        with:
+          artifacts: ${{ github.workspace }}/dist/*
+          tag: ${{ steps.release-version.outputs.RELEASE_VERSION }}
+          commit: ${{ github.sha }}
+          omitBody: true
+
+      - name: Publish package
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 6a30627..639589a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,59 @@
+# Created by https://www.toptal.com/developers/gitignore/api/linux,macos,direnv,python,windows,visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,direnv,python,windows,visualstudiocode
+
+### direnv ###
+.direnv
+.envrc
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### Python ###
 # Byte-compiled / optimized / DLL files
 __pycache__/
 *.py[cod]
@@ -152,5 +208,66 @@ dmypy.json
 # Cython debug symbols
 cython_debug/
 
-# VSCode
-.vscode/
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/linux,macos,direnv,python,windows,visualstudiocode
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e66fd6b..fe8b9ab 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,15 +18,11 @@ repos:
       - id: pyupgrade
         args: ["--py38-plus", "--keep-runtime-typing"]
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.1.11
+    rev: v0.2.0
     hooks:
       - id: ruff
         args: ["--fix"]
-  - repo: https://github.com/psf/black
-    rev: 23.12.1
-    hooks:
-      - id: black
-        language_version: python
+      - id: ruff-format
   - repo: https://github.com/pre-commit/mirrors-mypy
     rev: v1.8.0
     hooks:
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..116de13
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,12 @@
+{
+  "recommendations": [
+    "charliermarsh.ruff",
+    "DavidAnson.vscode-markdownlint",
+    "EditorConfig.EditorConfig",
+    "esbenp.prettier-vscode",
+    "ms-python.debugpy",
+    "ms-python.python",
+    "ms-python.vscode-pylance",
+    "tamasfe.even-better-toml"
+  ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..2d69f4a
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,21 @@
+{
+  "[json]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[python]": {
+    "editor.codeActionsOnSave": {
+      "source.fixAll": "always",
+      "source.organizeImports": "always"
+    },
+    "editor.defaultFormatter": "charliermarsh.ruff",
+    "editor.formatOnSave": true,
+    "editor.inlineSuggest.enabled": true
+  },
+  "python.testing.unittestEnabled": false,
+  "python.testing.pytestEnabled": true,
+  "markdownlint.config": {
+    "ul-indent": {
+      "indent": 4
+    }
+  }
+}
diff --git a/README.md b/README.md
index 373a4a6..624b40f 100644
--- a/README.md
+++ b/README.md
@@ -14,26 +14,36 @@ When parsing cql2-text to cql2-json.
     - `... NOT IN ...` become `NOT ... IN ...`
     - `... IS NOT NULL` becomes `NOT ... IS NULL`
 - Negative arithmetic operands become a multiply by -1: `{"op": "*", "params": [-1, <arithmetic_operand>]}`
+- Within a Character Literal, the character (`'` or `\`) used to escape a single quote is not preserved.
+    - Any `''` will become `\'`
 
 The cql2-text output from the pydantic models is opinionated and explicit. These choices have been made to keep the logic simple while ensuring the correctness of the output.
 
 - All property names are double quoted `"`.
+- Character Literals escape single quotes with a backslash `\'`.
 - Parenthesis `()` are placed around all comparison and arithmetic operations.
-    - This means that many outputs include a set of parentheses around the whole string. While this is not ideal, it is also not incorrect. When more tests are in place, they can be used to determine if a safe and easy way exists to remove them.
+    - This means that many outputs include a set of parentheses around the whole string.
+    - This may not be not ideal, but it is also not incorrect.
+    - Additional testing may be done in the future to determine if a safe and easy way exists to remove them.
 - Timestamps always contain decimal seconds out to 6 decimal places even when 0: `.000000`. It uses `strftime` with `%f` currently. Logic may be added later to adjust this.
 - Floats ending in `.0` will include the `.0` in the text. Where other libraries such as `shapely` will not include them in WKT.
 
 The cql2-text spec was not strictly followed for WKT. Some tweaks were made to increase it is compatible with `geojson-pydantic`, as well as accept the WKT output.
 
-- Added optional `Z` to each geometry. It doesn't enforce 2d / 3d, just allows the character to be there.
+- Added optional `Z` to each geometry.
+    - This does not enforce 2d / 3d, just allows the character to be there.
 - LineString coordinates require a minimum of 2 coordinates.
-- Added 'Linear Ring' for use in Polygons with a minimum of 4 coordinates. It doesn't enforce the ring being closed, just that it has enough coordinates to be one.
+- Added 'Linear Ring' for use in Polygons with a minimum of 4 coordinates.
+    - This does not enforce the ring being closed, just that it contains enough coordinates to be one.
 - Moved BBOX so it cannot be included in GeometryCollection.
-- Moved GeometryCollection to not allow nesting, until support is added to `geojson-pydantic`.
+- Added alternative MultiPoint syntax to maintain compatibility with other spatial tools and libraries.
+    - Allows reading of `MUTLIPOINT(0 0, 1 1)` without the inner parenthesis.
+    - This may be removed in the future if it is no longer necessary for compatibility. Removal will be considered a breaking change and be versioned as such.
+    - Note: Output will always include the inner parenthesis (via `geojson-pydantic`).
 
 There are a few things which **may** be issues with the spec but have not been fully addressed yet.
 
-- (Partially addressed) `spatial_literal` includes `geometry_collection` and `bbox`, and `geometry_collection` allows for all `spatial_literal` within it. But `bbox` does not seem to be a part of WKT. And at least within GeoJSON, nested `GeometryCollection` "SHOULD be avoided". This would mean the `cql2-text -> cql2-json` conversion would break where `geojson-pydantic` doesn't accept these cases.
+- `spatial_literal` includes `bbox`, and `geometry_collection` allows for all `spatial_literal` within it. But `bbox` does not seem to be a part of WKT. This would mean the `cql2-text -> cql2-json` conversion would break where `geojson-pydantic` doesn't accept these cases.
 - The spec does not allow for `EMPTY` geometries.
 
 ## Testing
@@ -45,7 +55,6 @@ Each file in `tests/data/json/` is a standalone cql2-json example. There will be
 While 100% of the lines of code are covered, more complex examples with more nested logic will be added in the future. As well as more variety to various inputs, the current examples are mostly PropertyRef and numbers. Such as:
 
 - More complex identifiers with `_`, `.`, `:`, and non ascii letters.
-- Character literals with escaped quote.
 - Deeply nested logic.
 - Each type of `scalar_expression` on each side of a `binary_comparison_predicate`, etc.
 
diff --git a/poetry.lock b/poetry.lock
index adedf15..98db1c4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -35,33 +35,33 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
 
 [[package]]
 name = "black"
-version = "23.12.1"
+version = "24.1.1"
 description = "The uncompromising code formatter."
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
-    {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
-    {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
-    {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
-    {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
-    {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
-    {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
-    {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
-    {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
-    {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
-    {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
-    {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
-    {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
-    {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
-    {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
-    {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
-    {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
-    {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
-    {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
-    {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
-    {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
-    {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
+    {file = "black-24.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c"},
+    {file = "black-24.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445"},
+    {file = "black-24.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a"},
+    {file = "black-24.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4"},
+    {file = "black-24.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7"},
+    {file = "black-24.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8"},
+    {file = "black-24.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161"},
+    {file = "black-24.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d"},
+    {file = "black-24.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8"},
+    {file = "black-24.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e"},
+    {file = "black-24.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6"},
+    {file = "black-24.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b"},
+    {file = "black-24.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62"},
+    {file = "black-24.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5"},
+    {file = "black-24.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6"},
+    {file = "black-24.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717"},
+    {file = "black-24.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9"},
+    {file = "black-24.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024"},
+    {file = "black-24.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2"},
+    {file = "black-24.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac"},
+    {file = "black-24.1.1-py3-none-any.whl", hash = "sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168"},
+    {file = "black-24.1.1.tar.gz", hash = "sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b"},
 ]
 
 [package.dependencies]
@@ -79,6 +79,28 @@ 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 = "blinker"
+version = "1.7.0"
+description = "Fast, simple object-to-object and broadcast signaling"
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"},
+    {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"},
+]
+
+[[package]]
+name = "certifi"
+version = "2024.2.2"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = true
+python-versions = ">=3.6"
+files = [
+    {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
+    {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
+]
+
 [[package]]
 name = "cfgv"
 version = "3.4.0"
@@ -90,6 +112,105 @@ files = [
     {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 = true
+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"
@@ -117,63 +238,63 @@ files = [
 
 [[package]]
 name = "coverage"
-version = "7.4.0"
+version = "7.4.1"
 description = "Code coverage measurement for Python"
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"},
-    {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"},
-    {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"},
-    {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"},
-    {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"},
-    {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"},
-    {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"},
-    {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"},
-    {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"},
-    {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"},
-    {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"},
-    {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"},
-    {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"},
-    {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"},
-    {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"},
-    {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"},
-    {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"},
-    {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"},
-    {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"},
-    {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"},
-    {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"},
-    {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"},
-    {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"},
-    {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"},
-    {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"},
-    {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"},
-    {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"},
-    {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"},
-    {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"},
-    {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"},
-    {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"},
-    {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"},
-    {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"},
-    {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"},
-    {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"},
-    {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"},
-    {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"},
-    {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"},
-    {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"},
-    {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"},
-    {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"},
-    {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"},
-    {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"},
-    {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"},
-    {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"},
-    {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"},
-    {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"},
-    {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"},
-    {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"},
-    {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"},
-    {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"},
-    {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"},
+    {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"},
+    {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"},
+    {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"},
+    {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"},
+    {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"},
+    {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"},
+    {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"},
+    {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"},
+    {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"},
+    {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"},
+    {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"},
+    {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"},
+    {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"},
+    {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"},
+    {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"},
+    {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"},
+    {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"},
+    {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"},
+    {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"},
+    {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"},
+    {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"},
+    {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"},
+    {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"},
+    {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"},
+    {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"},
+    {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"},
+    {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"},
+    {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"},
+    {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"},
+    {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"},
+    {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"},
+    {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"},
+    {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"},
+    {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"},
+    {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"},
+    {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"},
+    {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"},
+    {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"},
+    {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"},
+    {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"},
+    {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"},
+    {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"},
+    {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"},
+    {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"},
+    {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"},
+    {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"},
+    {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"},
+    {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"},
+    {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"},
+    {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"},
+    {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"},
+    {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"},
 ]
 
 [package.dependencies]
@@ -182,6 +303,72 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1
 [package.extras]
 toml = ["tomli"]
 
+[[package]]
+name = "dash"
+version = "2.15.0"
+description = "A Python framework for building reactive web-apps. Developed by Plotly."
+optional = true
+python-versions = ">=3.6"
+files = [
+    {file = "dash-2.15.0-py3-none-any.whl", hash = "sha256:df1882bbf613e4ca4372281c8facbeb68e97d76720336b051bf84c75d2de8588"},
+    {file = "dash-2.15.0.tar.gz", hash = "sha256:d38891337fc855d5673f75e5346354daa063c4ff45a8a6a21f25e858fcae41c2"},
+]
+
+[package.dependencies]
+dash-core-components = "2.0.0"
+dash-html-components = "2.0.0"
+dash-table = "5.0.0"
+Flask = ">=1.0.4,<3.1"
+importlib-metadata = {version = "*", markers = "python_version >= \"3.7\""}
+nest-asyncio = "*"
+plotly = ">=5.0.0"
+requests = "*"
+retrying = "*"
+setuptools = "*"
+typing-extensions = ">=4.1.1"
+Werkzeug = "<3.1"
+
+[package.extras]
+celery = ["celery[redis] (>=5.1.2)", "importlib-metadata (<5)", "redis (>=3.5.3)"]
+ci = ["black (==21.6b0)", "black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==3.9.2)", "flaky (==3.7.0)", "flask-talisman (==1.0.0)", "isort (==4.3.21)", "jupyterlab (<4.0.0)", "mimesis (<=11.1.0)", "mock (==4.0.3)", "numpy (<=1.25.2)", "openpyxl", "orjson (==3.5.4)", "orjson (==3.6.7)", "pandas (==1.1.5)", "pandas (>=1.4.0)", "preconditions", "pyarrow", "pyarrow (<3)", "pylint (==2.13.5)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "xlrd (<2)", "xlrd (>=2.0.1)"]
+compress = ["flask-compress"]
+dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"]
+diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"]
+testing = ["beautifulsoup4 (>=4.8.2)", "cryptography (<3.4)", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"]
+
+[[package]]
+name = "dash-core-components"
+version = "2.0.0"
+description = "Core component suite for Dash"
+optional = true
+python-versions = "*"
+files = [
+    {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"},
+    {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"},
+]
+
+[[package]]
+name = "dash-html-components"
+version = "2.0.0"
+description = "Vanilla HTML components for Dash"
+optional = true
+python-versions = "*"
+files = [
+    {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"},
+    {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"},
+]
+
+[[package]]
+name = "dash-table"
+version = "5.0.0"
+description = "Dash table"
+optional = true
+python-versions = "*"
+files = [
+    {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"},
+    {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"},
+]
+
 [[package]]
 name = "distlib"
 version = "0.3.8"
@@ -223,15 +410,38 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1
 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
 typing = ["typing-extensions (>=4.8)"]
 
+[[package]]
+name = "flask"
+version = "3.0.1"
+description = "A simple framework for building complex web applications."
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "flask-3.0.1-py3-none-any.whl", hash = "sha256:ca631a507f6dfe6c278ae20112cea3ff54ff2216390bf8880f6b035a5354af13"},
+    {file = "flask-3.0.1.tar.gz", hash = "sha256:6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403"},
+]
+
+[package.dependencies]
+blinker = ">=1.6.2"
+click = ">=8.1.3"
+importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
+itsdangerous = ">=2.1.2"
+Jinja2 = ">=3.1.2"
+Werkzeug = ">=3.0.0"
+
+[package.extras]
+async = ["asgiref (>=3.2)"]
+dotenv = ["python-dotenv"]
+
 [[package]]
 name = "geojson-pydantic"
-version = "1.0.1"
+version = "1.0.2"
 description = "Pydantic data models for the GeoJSON spec."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "geojson_pydantic-1.0.1-py3-none-any.whl", hash = "sha256:da8c15f15a0a9fc3e0af0253f0c2bb8a948f95ece9a0356f43d4738fa2be5107"},
-    {file = "geojson_pydantic-1.0.1.tar.gz", hash = "sha256:a996ffccd5a016d3acb4a0c6aac941d2c569e3c6163d5ce6a04b61ee131c8f94"},
+    {file = "geojson_pydantic-1.0.2-py3-none-any.whl", hash = "sha256:d7a7f8ff4ca27298f62d1af182f69ec85f9990b9db80fa7e0161be1de2a9ae0f"},
+    {file = "geojson_pydantic-1.0.2.tar.gz", hash = "sha256:3179d665ad8763670681b73aded2228df20a1164951e9d7075be8f370eb681f4"},
 ]
 
 [package.dependencies]
@@ -242,20 +452,48 @@ dev = ["pre-commit"]
 docs = ["mkdocs", "mkdocs-material", "pygments"]
 test = ["pytest", "pytest-cov", "shapely"]
 
+[[package]]
+name = "hypofuzz"
+version = "24.2.1"
+description = "Adaptive fuzzing for property-based tests"
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "hypofuzz-24.2.1-py3-none-any.whl", hash = "sha256:fa89dadc442df6719843a5b8008eb26481006acf6772165e28b138935e66edd5"},
+    {file = "hypofuzz-24.2.1.tar.gz", hash = "sha256:aef14460f725cc8e8acfdbd8f140b6d68f55c9e451ccece85eaf9b83be8db5be"},
+]
+
+[package.dependencies]
+black = ">=23.3.0"
+coverage = ">=5.2.1"
+dash = ">=2.0.0"
+hypothesis = {version = ">=6.93.2", extras = ["cli"]}
+libcst = ">=1.0.0"
+pandas = ">=1.0.0"
+psutil = ">=3.0.0"
+pytest = ">=6.0.1"
+requests = ">=2.24.0"
+
+[package.extras]
+pytrace = ["flask-cors (>=3.0.10)", "pycrunch-trace (>=0.1.6)"]
+
 [[package]]
 name = "hypothesis"
-version = "6.92.6"
+version = "6.97.4"
 description = "A library for property-based testing"
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "hypothesis-6.92.6-py3-none-any.whl", hash = "sha256:f8af0545a5a504abce9cb1ec1dbf6d737c356f22f002036ffee0421a2119b7e5"},
-    {file = "hypothesis-6.92.6.tar.gz", hash = "sha256:fbb69c8e704427eadfd91fa81da8a82f6938dfcc9b4364027e476590bb7d7d8c"},
+    {file = "hypothesis-6.97.4-py3-none-any.whl", hash = "sha256:9069fe3fb18d9b7dd218bd69ab50bbc66426819dfac7cc7168ba85034d98a4df"},
+    {file = "hypothesis-6.97.4.tar.gz", hash = "sha256:28ff724fa81ccc55f64f0f1eb06e4a75db6a195fe0857e9b3184cf4ff613a103"},
 ]
 
 [package.dependencies]
 attrs = ">=22.2.0"
+black = {version = ">=19.10b0", optional = true, markers = "extra == \"cli\""}
+click = {version = ">=7.0", optional = true, markers = "extra == \"cli\""}
 exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+rich = {version = ">=9.0.0", optional = true, markers = "extra == \"cli\""}
 sortedcontainers = ">=2.1.0,<3.0.0"
 
 [package.extras]
@@ -288,6 +526,36 @@ files = [
 [package.extras]
 license = ["ukkonen"]
 
+[[package]]
+name = "idna"
+version = "3.6"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = true
+python-versions = ">=3.5"
+files = [
+    {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
+    {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "7.0.1"
+description = "Read metadata from Python packages"
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
+    {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
+]
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+perf = ["ipython"]
+testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
+
 [[package]]
 name = "iniconfig"
 version = "2.0.0"
@@ -299,15 +567,43 @@ files = [
     {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
 ]
 
+[[package]]
+name = "itsdangerous"
+version = "2.1.2"
+description = "Safely pass data to untrusted environments and back."
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+    {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.3"
+description = "A very fast and expressive template engine."
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
+    {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
 [[package]]
 name = "lark"
-version = "1.1.8"
+version = "1.1.9"
 description = "a modern parsing library"
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "lark-1.1.8-py3-none-any.whl", hash = "sha256:7d2c221a66a8165f3f81aacb958d26033d40d972fdb70213ab0a2e0627e29c86"},
-    {file = "lark-1.1.8.tar.gz", hash = "sha256:7ef424db57f59c1ffd6f0d4c2b705119927f566b68c0fe1942dddcc0e44391a5"},
+    {file = "lark-1.1.9-py3-none-any.whl", hash = "sha256:a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db"},
+    {file = "lark-1.1.9.tar.gz", hash = "sha256:15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba"},
 ]
 
 [package.extras]
@@ -316,6 +612,158 @@ interegular = ["interegular (>=0.3.1,<0.4.0)"]
 nearley = ["js2py"]
 regex = ["regex"]
 
+[[package]]
+name = "libcst"
+version = "1.1.0"
+description = "A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7, 3.8, 3.9, and 3.10 programs."
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "libcst-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:63f75656fd733dc20354c46253fde3cf155613e37643c3eaf6f8818e95b7a3d1"},
+    {file = "libcst-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ae11eb1ea55a16dc0cdc61b41b29ac347da70fec14cc4381248e141ee2fbe6c"},
+    {file = "libcst-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bc745d0c06420fe2644c28d6ddccea9474fb68a2135904043676deb4fa1e6bc"},
+    {file = "libcst-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1f2da45f1c45634090fd8672c15e0159fdc46853336686959b2d093b6e10fa"},
+    {file = "libcst-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:003e5e83a12eed23542c4ea20fdc8de830887cc03662432bb36f84f8c4841b81"},
+    {file = "libcst-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:3ebbb9732ae3cc4ae7a0e97890bed0a57c11d6df28790c2b9c869f7da653c7c7"},
+    {file = "libcst-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d68c34e3038d3d1d6324eb47744cbf13f2c65e1214cf49db6ff2a6603c1cd838"},
+    {file = "libcst-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9dffa1795c2804d183efb01c0f1efd20a7831db6a21a0311edf90b4100d67436"},
+    {file = "libcst-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc9b6ac36d7ec9db2f053014ea488086ca2ed9c322be104fbe2c71ca759da4bb"},
+    {file = "libcst-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b7a38ec4c1c009ac39027d51558b52851fb9234669ba5ba62283185963a31c"},
+    {file = "libcst-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5297a16e575be8173185e936b7765c89a3ca69d4ae217a4af161814a0f9745a7"},
+    {file = "libcst-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:7ccaf53925f81118aeaadb068a911fac8abaff608817d7343da280616a5ca9c1"},
+    {file = "libcst-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:75816647736f7e09c6120bdbf408456f99b248d6272277eed9a58cf50fb8bc7d"},
+    {file = "libcst-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8f26250f87ca849a7303ed7a4fd6b2c7ac4dec16b7d7e68ca6a476d7c9bfcdb"},
+    {file = "libcst-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d37326bd6f379c64190a28947a586b949de3a76be00176b0732c8ee87d67ebe"},
+    {file = "libcst-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d8cf974cfa2487b28f23f56c4bff90d550ef16505e58b0dca0493d5293784b"},
+    {file = "libcst-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d1271403509b0a4ee6ff7917c2d33b5a015f44d1e208abb1da06ba93b2a378"},
+    {file = "libcst-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bca1841693941fdd18371824bb19a9702d5784cd347cb8231317dbdc7062c5bc"},
+    {file = "libcst-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f36f592e035ef84f312a12b75989dde6a5f6767fe99146cdae6a9ee9aff40dd0"},
+    {file = "libcst-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f561c9a84eca18be92f4ad90aa9bd873111efbea995449301719a1a7805dbc5c"},
+    {file = "libcst-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97fbc73c87e9040e148881041fd5ffa2a6ebf11f64b4ccb5b52e574b95df1a15"},
+    {file = "libcst-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99fdc1929703fd9e7408aed2e03f58701c5280b05c8911753a8d8619f7dfdda5"},
+    {file = "libcst-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bf69cbbab5016d938aac4d3ae70ba9ccb3f90363c588b3b97be434e6ba95403"},
+    {file = "libcst-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fe41b33aa73635b1651f64633f429f7aa21f86d2db5748659a99d9b7b1ed2a90"},
+    {file = "libcst-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73c086705ed34dbad16c62c9adca4249a556c1b022993d511da70ea85feaf669"},
+    {file = "libcst-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a07ecfabbbb8b93209f952a365549e65e658831e9231649f4f4e4263cad24b1"},
+    {file = "libcst-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c653d9121d6572d8b7f8abf20f88b0a41aab77ff5a6a36e5a0ec0f19af0072e8"},
+    {file = "libcst-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f1cd308a4c2f71d5e4eec6ee693819933a03b78edb2e4cc5e3ad1afd5fb3f07"},
+    {file = "libcst-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8afb6101b8b3c86c5f9cec6b90ab4da16c3c236fe7396f88e8b93542bb341f7c"},
+    {file = "libcst-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:d22d1abfe49aa60fc61fa867e10875a9b3024ba5a801112f4d7ba42d8d53242e"},
+    {file = "libcst-1.1.0.tar.gz", hash = "sha256:0acbacb9a170455701845b7e940e2d7b9519db35a86768d86330a0b0deae1086"},
+]
+
+[package.dependencies]
+pyyaml = ">=5.2"
+typing-extensions = ">=3.7.4.2"
+typing-inspect = ">=0.4.0"
+
+[package.extras]
+dev = ["Sphinx (>=5.1.1)", "black (==23.9.1)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==2.0.0.post1)", "flake8 (>=3.7.8,<5)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.2)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<0.16)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.18)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.2.0)", "usort (==1.0.7)"]
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
+    {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
+]
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark"]
+code-style = ["pre-commit (>=3.0,<4.0)"]
+compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
+linkify = ["linkify-it-py (>=1,<3)"]
+plugins = ["mdit-py-plugins"]
+profiling = ["gprof2dot"]
+rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.4"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"},
+    {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"},
+    {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"},
+    {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"},
+    {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"},
+    {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"},
+    {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"},
+    {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"},
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+    {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+
 [[package]]
 name = "mypy"
 version = "1.8.0"
@@ -374,6 +822,17 @@ files = [
     {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 = true
+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.8.0"
@@ -388,6 +847,88 @@ files = [
 [package.dependencies]
 setuptools = "*"
 
+[[package]]
+name = "numpy"
+version = "1.24.4"
+description = "Fundamental package for array computing in Python"
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"},
+    {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"},
+    {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"},
+    {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"},
+    {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"},
+    {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"},
+    {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"},
+    {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"},
+    {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"},
+    {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"},
+    {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"},
+    {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"},
+    {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"},
+    {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"},
+    {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"},
+    {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"},
+    {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"},
+    {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"},
+    {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"},
+    {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"},
+    {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"},
+    {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"},
+    {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"},
+    {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"},
+    {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"},
+    {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"},
+    {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"},
+    {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"},
+]
+
+[[package]]
+name = "numpy"
+version = "1.26.3"
+description = "Fundamental package for array computing in Python"
+optional = true
+python-versions = ">=3.9"
+files = [
+    {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"},
+    {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"},
+    {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"},
+    {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"},
+    {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"},
+    {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"},
+    {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"},
+    {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"},
+    {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"},
+    {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"},
+    {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"},
+    {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"},
+    {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"},
+    {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"},
+    {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"},
+    {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"},
+    {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"},
+    {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"},
+    {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"},
+    {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"},
+    {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"},
+    {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"},
+    {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"},
+    {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"},
+    {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"},
+    {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"},
+    {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"},
+    {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"},
+    {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"},
+    {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"},
+    {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"},
+    {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"},
+    {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"},
+    {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"},
+    {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"},
+    {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"},
+]
+
 [[package]]
 name = "packaging"
 version = "23.2"
@@ -399,6 +940,73 @@ files = [
     {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
 ]
 
+[[package]]
+name = "pandas"
+version = "2.0.3"
+description = "Powerful data structures for data analysis, time series, and statistics"
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"},
+    {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"},
+    {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"},
+    {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"},
+    {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"},
+    {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"},
+    {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"},
+    {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"},
+    {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"},
+    {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"},
+    {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"},
+    {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"},
+    {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"},
+    {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"},
+    {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"},
+    {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"},
+    {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"},
+    {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"},
+    {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"},
+    {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"},
+    {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"},
+    {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"},
+    {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"},
+    {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"},
+    {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"},
+]
+
+[package.dependencies]
+numpy = [
+    {version = ">=1.20.3", markers = "python_version < \"3.10\""},
+    {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""},
+    {version = ">=1.23.2", markers = "python_version >= \"3.11\""},
+]
+python-dateutil = ">=2.8.2"
+pytz = ">=2020.1"
+tzdata = ">=2022.1"
+
+[package.extras]
+all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"]
+aws = ["s3fs (>=2021.08.0)"]
+clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"]
+compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"]
+computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"]
+excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"]
+feather = ["pyarrow (>=7.0.0)"]
+fss = ["fsspec (>=2021.07.0)"]
+gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"]
+hdf5 = ["tables (>=3.6.1)"]
+html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"]
+mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"]
+output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"]
+parquet = ["pyarrow (>=7.0.0)"]
+performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"]
+plot = ["matplotlib (>=3.6.1)"]
+postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"]
+spss = ["pyreadstat (>=1.1.2)"]
+sql-other = ["SQLAlchemy (>=1.4.16)"]
+test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
+xml = ["lxml (>=4.6.3)"]
+
 [[package]]
 name = "pathspec"
 version = "0.12.1"
@@ -412,28 +1020,43 @@ files = [
 
 [[package]]
 name = "platformdirs"
-version = "4.1.0"
+version = "4.2.0"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
-    {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
+    {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"},
+    {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"},
 ]
 
 [package.extras]
-docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
+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)"]
+
+[[package]]
+name = "plotly"
+version = "5.18.0"
+description = "An open-source, interactive data visualization library for Python"
+optional = true
+python-versions = ">=3.6"
+files = [
+    {file = "plotly-5.18.0-py3-none-any.whl", hash = "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de"},
+    {file = "plotly-5.18.0.tar.gz", hash = "sha256:360a31e6fbb49d12b007036eb6929521343d6bee2236f8459915821baefa2cbb"},
+]
+
+[package.dependencies]
+packaging = "*"
+tenacity = ">=6.2.0"
 
 [[package]]
 name = "pluggy"
-version = "1.3.0"
+version = "1.4.0"
 description = "plugin and hook calling mechanisms for python"
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
-    {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
+    {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
+    {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
 ]
 
 [package.extras]
@@ -458,20 +1081,48 @@ nodeenv = ">=0.11.1"
 pyyaml = ">=5.1"
 virtualenv = ">=20.10.0"
 
+[[package]]
+name = "psutil"
+version = "5.9.8"
+description = "Cross-platform lib for process and system monitoring in Python."
+optional = true
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+files = [
+    {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
+    {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
+    {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
+    {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
+    {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
+    {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
+    {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
+    {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
+    {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
+    {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
+    {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
+    {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
+    {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
+    {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
+    {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
+    {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},
+]
+
+[package.extras]
+test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
+
 [[package]]
 name = "pydantic"
-version = "2.5.3"
+version = "2.6.0"
 description = "Data validation using Python type hints"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"},
-    {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"},
+    {file = "pydantic-2.6.0-py3-none-any.whl", hash = "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae"},
+    {file = "pydantic-2.6.0.tar.gz", hash = "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf"},
 ]
 
 [package.dependencies]
 annotated-types = ">=0.4.0"
-pydantic-core = "2.14.6"
+pydantic-core = "2.16.1"
 typing-extensions = ">=4.6.1"
 
 [package.extras]
@@ -479,121 +1130,110 @@ email = ["email-validator (>=2.0.0)"]
 
 [[package]]
 name = "pydantic-core"
-version = "2.14.6"
+version = "2.16.1"
 description = ""
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
 files = [
-    {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"},
-    {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"},
-    {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"},
-    {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"},
-    {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"},
-    {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"},
-    {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"},
-    {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"},
-    {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"},
-    {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"},
-    {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"},
-    {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"},
-    {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"},
-    {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"},
-    {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"},
-    {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"},
-    {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"},
-    {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"},
-    {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"},
-    {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"},
-    {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"},
-    {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"},
-    {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"},
-    {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"},
-    {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"},
-    {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"},
-    {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"},
-    {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"},
-    {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"},
-    {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"},
-    {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388"},
+    {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7"},
+    {file = "pydantic_core-2.16.1-cp310-none-win32.whl", hash = "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4"},
+    {file = "pydantic_core-2.16.1-cp310-none-win_amd64.whl", hash = "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7"},
+    {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae"},
+    {file = "pydantic_core-2.16.1-cp311-none-win32.whl", hash = "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1"},
+    {file = "pydantic_core-2.16.1-cp311-none-win_amd64.whl", hash = "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c"},
+    {file = "pydantic_core-2.16.1-cp311-none-win_arm64.whl", hash = "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8"},
+    {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f"},
+    {file = "pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212"},
+    {file = "pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f"},
+    {file = "pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:502c062a18d84452858f8aea1e520e12a4d5228fc3621ea5061409d666ea1706"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8c032ccee90b37b44e05948b449a2d6baed7e614df3d3f47fe432c952c21b60"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920f4633bee43d7a2818e1a1a788906df5a17b7ab6fe411220ed92b42940f818"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f5d37ff01edcbace53a402e80793640c25798fb7208f105d87a25e6fcc9ea06"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:399166f24c33a0c5759ecc4801f040dbc87d412c1a6d6292b2349b4c505effc9"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac89ccc39cd1d556cc72d6752f252dc869dde41c7c936e86beac5eb555041b66"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73802194f10c394c2bedce7a135ba1d8ba6cff23adf4217612bfc5cf060de34c"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fa00fa24ffd8c31fac081bf7be7eb495be6d248db127f8776575a746fa55c95"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:601d3e42452cd4f2891c13fa8c70366d71851c1593ed42f57bf37f40f7dca3c8"},
+    {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07982b82d121ed3fc1c51faf6e8f57ff09b1325d2efccaa257dd8c0dd937acca"},
+    {file = "pydantic_core-2.16.1-cp38-none-win32.whl", hash = "sha256:d0bf6f93a55d3fa7a079d811b29100b019784e2ee6bc06b0bb839538272a5610"},
+    {file = "pydantic_core-2.16.1-cp38-none-win_amd64.whl", hash = "sha256:fbec2af0ebafa57eb82c18c304b37c86a8abddf7022955d1742b3d5471a6339e"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a497be217818c318d93f07e14502ef93d44e6a20c72b04c530611e45e54c2196"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:694a5e9f1f2c124a17ff2d0be613fd53ba0c26de588eb4bdab8bca855e550d95"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d4dfc66abea3ec6d9f83e837a8f8a7d9d3a76d25c9911735c76d6745950e62c"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8655f55fe68c4685673265a650ef71beb2d31871c049c8b80262026f23605ee3"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21e3298486c4ea4e4d5cc6fb69e06fb02a4e22089304308817035ac006a7f506"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71b4a48a7427f14679f0015b13c712863d28bb1ab700bd11776a5368135c7d60"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dca874e35bb60ce4f9f6665bfbfad050dd7573596608aeb9e098621ac331dc"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa496cd45cda0165d597e9d6f01e36c33c9508f75cf03c0a650018c5048f578e"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5317c04349472e683803da262c781c42c5628a9be73f4750ac7d13040efb5d2d"},
+    {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42c29d54ed4501a30cd71015bf982fa95e4a60117b44e1a200290ce687d3e640"},
+    {file = "pydantic_core-2.16.1-cp39-none-win32.whl", hash = "sha256:ba07646f35e4e49376c9831130039d1b478fbfa1215ae62ad62d2ee63cf9c18f"},
+    {file = "pydantic_core-2.16.1-cp39-none-win_amd64.whl", hash = "sha256:2133b0e412a47868a358713287ff9f9a328879da547dc88be67481cdac529118"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc"},
+    {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:adf28099d061a25fbcc6531febb7a091e027605385de9fe14dd6a97319d614cf"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:644904600c15816a1f9a1bafa6aab0d21db2788abcdf4e2a77951280473f33e1"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bce04f09f0552b66fca0c4e10da78d17cb0e71c205864bab4e9595122cb9d9"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877045a7969ace04d59516d5d6a7dee13106822f99a5d8df5e6822941f7bedc8"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9c46e556ee266ed3fb7b7a882b53df3c76b45e872fdab8d9cf49ae5e91147fd7"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4eebbd049008eb800f519578e944b8dc8e0f7d59a5abb5924cc2d4ed3a1834ff"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c0be58529d43d38ae849a91932391eb93275a06b93b79a8ab828b012e916a206"},
+    {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b1fc07896fc1851558f532dffc8987e526b682ec73140886c831d773cef44b76"},
+    {file = "pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34"},
 ]
 
 [package.dependencies]
 typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
 
+[[package]]
+name = "pygments"
+version = "2.17.2"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
+    {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
+]
+
+[package.extras]
+plugins = ["importlib-metadata"]
+windows-terminal = ["colorama (>=0.4.6)"]
+
 [[package]]
 name = "pytest"
 version = "7.4.4"
@@ -634,6 +1274,31 @@ pytest = ">=4.6"
 [package.extras]
 testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
 
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+optional = true
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+    {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+    {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2024.1"
+description = "World timezone definitions, modern and historical"
+optional = true
+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 = "pyyaml"
 version = "6.0.1"
@@ -693,30 +1358,84 @@ files = [
     {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
 ]
 
+[[package]]
+name = "requests"
+version = "2.31.0"
+description = "Python HTTP for Humans."
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+    {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+]
+
+[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 = "retrying"
+version = "1.3.4"
+description = "Retrying"
+optional = true
+python-versions = "*"
+files = [
+    {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"},
+    {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"},
+]
+
+[package.dependencies]
+six = ">=1.7.0"
+
+[[package]]
+name = "rich"
+version = "13.7.0"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+optional = true
+python-versions = ">=3.7.0"
+files = [
+    {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"},
+    {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"},
+]
+
+[package.dependencies]
+markdown-it-py = ">=2.2.0"
+pygments = ">=2.13.0,<3.0.0"
+typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<9)"]
+
 [[package]]
 name = "ruff"
-version = "0.1.11"
+version = "0.2.0"
 description = "An extremely fast Python linter and code formatter, written in Rust."
 optional = true
 python-versions = ">=3.7"
 files = [
-    {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a7f772696b4cdc0a3b2e527fc3c7ccc41cdcb98f5c80fdd4f2b8c50eb1458196"},
-    {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934832f6ed9b34a7d5feea58972635c2039c7a3b434fe5ba2ce015064cb6e955"},
-    {file = "ruff-0.1.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea0d3e950e394c4b332bcdd112aa566010a9f9c95814844a7468325290aabfd9"},
-    {file = "ruff-0.1.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bd4025b9c5b429a48280785a2b71d479798a69f5c2919e7d274c5f4b32c3607"},
-    {file = "ruff-0.1.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ad00662305dcb1e987f5ec214d31f7d6a062cae3e74c1cbccef15afd96611d"},
-    {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4b077ce83f47dd6bea1991af08b140e8b8339f0ba8cb9b7a484c30ebab18a23f"},
-    {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a88efecec23c37b11076fe676e15c6cdb1271a38f2b415e381e87fe4517f18"},
-    {file = "ruff-0.1.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b25093dad3b055667730a9b491129c42d45e11cdb7043b702e97125bcec48a1"},
-    {file = "ruff-0.1.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231d8fb11b2cc7c0366a326a66dafc6ad449d7fcdbc268497ee47e1334f66f77"},
-    {file = "ruff-0.1.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:09c415716884950080921dd6237767e52e227e397e2008e2bed410117679975b"},
-    {file = "ruff-0.1.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f58948c6d212a6b8d41cd59e349751018797ce1727f961c2fa755ad6208ba45"},
-    {file = "ruff-0.1.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:190a566c8f766c37074d99640cd9ca3da11d8deae2deae7c9505e68a4a30f740"},
-    {file = "ruff-0.1.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6464289bd67b2344d2a5d9158d5eb81025258f169e69a46b741b396ffb0cda95"},
-    {file = "ruff-0.1.11-py3-none-win32.whl", hash = "sha256:9b8f397902f92bc2e70fb6bebfa2139008dc72ae5177e66c383fa5426cb0bf2c"},
-    {file = "ruff-0.1.11-py3-none-win_amd64.whl", hash = "sha256:eb85ee287b11f901037a6683b2374bb0ec82928c5cbc984f575d0437979c521a"},
-    {file = "ruff-0.1.11-py3-none-win_arm64.whl", hash = "sha256:97ce4d752f964ba559c7023a86e5f8e97f026d511e48013987623915431c7ea9"},
-    {file = "ruff-0.1.11.tar.gz", hash = "sha256:f9d4d88cb6eeb4dfe20f9f0519bd2eaba8119bde87c3d5065c541dbae2b5a2cb"},
+    {file = "ruff-0.2.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:638ea3294f800d18bae84a492cb5a245c8d29c90d19a91d8e338937a4c27fca0"},
+    {file = "ruff-0.2.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3ff35433fcf4dff6d610738712152df6b7d92351a1bde8e00bd405b08b3d5759"},
+    {file = "ruff-0.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9faafbdcf4f53917019f2c230766da437d4fd5caecd12ddb68bb6a17d74399"},
+    {file = "ruff-0.2.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8153a3e4128ed770871c47545f1ae7b055023e0c222ff72a759f5a341ee06483"},
+    {file = "ruff-0.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a75a98ae989a27090e9c51f763990ad5bbc92d20626d54e9701c7fe597f399"},
+    {file = "ruff-0.2.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:87057dd2fdde297130ff99553be8549ca38a2965871462a97394c22ed2dfc19d"},
+    {file = "ruff-0.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d232f99d3ab00094ebaf88e0fb7a8ccacaa54cc7fa3b8993d9627a11e6aed7a"},
+    {file = "ruff-0.2.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d3c641f95f435fc6754b05591774a17df41648f0daf3de0d75ad3d9f099ab92"},
+    {file = "ruff-0.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3826fb34c144ef1e171b323ed6ae9146ab76d109960addca730756dc19dc7b22"},
+    {file = "ruff-0.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:eceab7d85d09321b4de18b62d38710cf296cb49e98979960a59c6b9307c18cfe"},
+    {file = "ruff-0.2.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:30ad74687e1f4a9ff8e513b20b82ccadb6bd796fe5697f1e417189c5cde6be3e"},
+    {file = "ruff-0.2.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7e3818698f8460bd0f8d4322bbe99db8327e9bc2c93c789d3159f5b335f47da"},
+    {file = "ruff-0.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:edf23041242c48b0d8295214783ef543847ef29e8226d9f69bf96592dba82a83"},
+    {file = "ruff-0.2.0-py3-none-win32.whl", hash = "sha256:e155147199c2714ff52385b760fe242bb99ea64b240a9ffbd6a5918eb1268843"},
+    {file = "ruff-0.2.0-py3-none-win_amd64.whl", hash = "sha256:ba918e01cdd21e81b07555564f40d307b0caafa9a7a65742e98ff244f5035c59"},
+    {file = "ruff-0.2.0-py3-none-win_arm64.whl", hash = "sha256:3fbaff1ba9564a2c5943f8f38bc221f04bac687cc7485e45237579fee7ccda79"},
+    {file = "ruff-0.2.0.tar.gz", hash = "sha256:63856b91837606c673537d2889989733d7dffde553828d3b0f0bacfa6def54be"},
 ]
 
 [[package]]
@@ -735,6 +1454,17 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments
 testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
 testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
 
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = true
+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 = "sortedcontainers"
 version = "2.4.0"
@@ -746,6 +1476,20 @@ files = [
     {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
 ]
 
+[[package]]
+name = "tenacity"
+version = "8.2.3"
+description = "Retry code until it succeeds"
+optional = true
+python-versions = ">=3.7"
+files = [
+    {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"},
+    {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"},
+]
+
+[package.extras]
+doc = ["reno", "sphinx", "tornado (>=4.5)"]
+
 [[package]]
 name = "tomli"
 version = "2.0.1"
@@ -768,6 +1512,49 @@ files = [
     {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
 ]
 
+[[package]]
+name = "typing-inspect"
+version = "0.9.0"
+description = "Runtime inspection utilities for typing module."
+optional = true
+python-versions = "*"
+files = [
+    {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"},
+    {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"},
+]
+
+[package.dependencies]
+mypy-extensions = ">=0.3.0"
+typing-extensions = ">=3.7.4"
+
+[[package]]
+name = "tzdata"
+version = "2023.4"
+description = "Provider of IANA time zone data"
+optional = true
+python-versions = ">=2"
+files = [
+    {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"},
+    {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.2.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"},
+    {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"},
+]
+
+[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.25.0"
@@ -788,11 +1575,44 @@ platformdirs = ">=3.9.1,<5"
 docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "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 = "werkzeug"
+version = "3.0.1"
+description = "The comprehensive WSGI web application library."
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
+    {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.1.1"
+
+[package.extras]
+watchdog = ["watchdog (>=2.3)"]
+
+[[package]]
+name = "zipp"
+version = "3.17.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = true
+python-versions = ">=3.8"
+files = [
+    {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
+    {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
+
 [extras]
 dev = ["black", "mypy", "pre-commit", "ruff"]
+fuzz = ["hypofuzz"]
 test = ["coverage", "hypothesis", "pytest", "pytest-cov"]
 
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.8"
-content-hash = "2bcac13a6812edf6a512448fb425f051e3f45c8e4d571e47e5ca409118bc986f"
+content-hash = "7344abca0e673dafa65b2f6f1f8d1ebcf23feee0df6137788f5d8923f921bdeb"
diff --git a/pycql2/__init__.py b/pycql2/__init__.py
index 3dc1f76..e69de29 100644
--- a/pycql2/__init__.py
+++ b/pycql2/__init__.py
@@ -1,1 +0,0 @@
-__version__ = "0.1.0"
diff --git a/pycql2/cql2.lark b/pycql2/cql2.lark
index b417035..b764c4a 100644
--- a/pycql2/cql2.lark
+++ b/pycql2/cql2.lark
@@ -11,7 +11,8 @@ start: boolean_expression
 // Splitting NOT into a separate rule does not result in parsing errors, and is more readable.
 ?boolean_factor: boolean_primary
                | _NOT boolean_primary                  -> not_
-?boolean_primary: predicate
+?boolean_primary: function
+                | predicate
                 | BOOLEAN_LITERAL
                 | "(" boolean_expression ")"
 
@@ -79,42 +80,42 @@ is_null_predicate: is_null_operand _IS _NULL        -> is_null
                 | function
 
 // Spatial Predicate
-spatial_predicate: SPATIAL_OPERATOR "(" geom_expression "," geom_expression ")"
+spatial_predicate: SPATIAL_FUNCTION "(" geom_expression "," geom_expression ")"
 ?geom_expression: spatial_instance
                 | property_name
                 | function
-SPATIAL_OPERATOR: "S_INTERSECTS"i
-                | "S_EQUALS"i
-                | "S_DISJOINT"i
-                | "S_TOUCHES"i
-                | "S_WITHIN"i
-                | "S_OVERLAPS"i
-                | "S_CROSSES"i
-                | "S_CONTAINS"i
+SPATIAL_FUNCTION.2: "S_INTERSECTS"i
+                  | "S_EQUALS"i
+                  | "S_DISJOINT"i
+                  | "S_TOUCHES"i
+                  | "S_WITHIN"i
+                  | "S_OVERLAPS"i
+                  | "S_CROSSES"i
+                  | "S_CONTAINS"i
 
 // Temporal Predicate
-temporal_predicate: TEMPORAL_OPERATOR "(" temporal_expression "," temporal_expression ")"
+temporal_predicate: TEMPORAL_FUNCTION "(" temporal_expression "," temporal_expression ")"
 ?temporal_expression: temporal_instance
                     | property_name
                     | function
-TEMPORAL_OPERATOR: "T_AFTER"i
-                 | "T_BEFORE"i
-                 | "T_CONTAINS"i
-                 | "T_DISJOINT"i
-                 | "T_DURING"i
-                 | "T_EQUALS"i
-                 | "T_FINISHEDBY"i
-                 | "T_FINISHES"i
-                 | "T_INTERSECTS"i
-                 | "T_MEETS"i
-                 | "T_METBY"i
-                 | "T_OVERLAPPEDBY"i
-                 | "T_OVERLAPS"i
-                 | "T_STARTEDBY"i
-                 | "T_STARTS"i
+TEMPORAL_FUNCTION.2: "T_AFTER"i
+                   | "T_BEFORE"i
+                   | "T_CONTAINS"i
+                   | "T_DISJOINT"i
+                   | "T_DURING"i
+                   | "T_EQUALS"i
+                   | "T_FINISHEDBY"i
+                   | "T_FINISHES"i
+                   | "T_INTERSECTS"i
+                   | "T_MEETS"i
+                   | "T_METBY"i
+                   | "T_OVERLAPPEDBY"i
+                   | "T_OVERLAPS"i
+                   | "T_STARTEDBY"i
+                   | "T_STARTS"i
 
 // Array Predicate
-array_predicate: ARRAY_OPERATOR "(" array_expression "," array_expression ")"
+array_predicate: ARRAY_FUNCTION "(" array_expression "," array_expression ")"
 ?array_expression: array
                  | property_name
                  | function
@@ -130,12 +131,14 @@ array: "(" ")"
               | property_name
               | function
 
-ARRAY_OPERATOR: "A_EQUALS"i
-              | "A_CONTAINS"i
-              | "A_CONTAINEDBY"i
-              | "A_OVERLAPS"i
+ARRAY_FUNCTION.2: "A_EQUALS"i
+                | "A_CONTAINS"i
+                | "A_CONTAINEDBY"i
+                | "A_OVERLAPS"i
 
 // Arithmetic definitions
+// This still uses the old definition with left recursion. The newer definiton
+// cannot parse filter57-alt01.txt.
 ?arithmetic_expression: arithmetic_term
                       | arithmetic_expression "+" arithmetic_term  -> plus
                       | arithmetic_expression "-" arithmetic_term  -> minus
@@ -155,14 +158,50 @@ ARRAY_OPERATOR: "A_EQUALS"i
 
 // Character literal, property name, and function name definitions
 ESCAPED_QUOTE: "''"
-CHARACTER_LITERAL: "'" (ALPHA | DIGIT | ESCAPED_QUOTE)* "'"
+             | "\'"
+CHARACTER_LITERAL: "'" (ALPHA | DIGIT | WHITESPACE | ESCAPED_QUOTE)* "'"
 
-ALPHA: /[\u0007-\u000D]/
-     | /[\u0020-\u0026]/
+// For detailed notes on character sets see:
+// https://github.com/opengeospatial/ogcapi-features/blob/master/cql2/standard/schema/cql2.bnf
+
+ALPHA: /[\u0007-\u0008]/
+     | /[\u0021-\u0026]/
      | /[\u0028-\u002F]/
-     | /[\u003A-\uD7FF]/
-     | /[\uE000-\uFFFD]/
-     | /[\U00010000-\U0010FFFF]/
+     | /[\u003A-\u0084]/
+     | /[\u0086-\u009F]/
+     | /[\u00A1-\u167F]/
+     | /[\u1681-\u1FFF]/
+     | /[\u200B-\u2027]/
+     | /[\u202A-\u202E]/
+     | /[\u2030-\u205E]/
+     | /[\u2060-\u2FFF]/
+     | /[\u3001-\uD7FF]/
+
+WHITESPACE: /\u0009/
+          | /\u000A/
+          | /\u000B/
+          | /\u000C/
+          | /\u000D/
+          | /\u0020/
+          | /\u0085/
+          | /\u00A0/
+          | /\u1680/
+          | /\u2000/
+          | /\u2001/
+          | /\u2002/
+          | /\u2003/
+          | /\u2004/
+          | /\u2005/
+          | /\u2006/
+          | /\u2007/
+          | /\u2008/
+          | /\u2009/
+          | /\u200A/
+          | /\u2028/
+          | /\u2029/
+          | /\u202F/
+          | /\u205F/
+          | /\u3000/
 
 // Identifier
 IDENTIFIER: IDENTIFIER_START IDENTIFIER_PART*
@@ -171,10 +210,10 @@ IDENTIFIER_PART: IDENTIFIER_START
                | DIGIT
                | /[\u0300-\u036F]/
                | /[\u203F-\u2040]/
-IDENTIFIER_START: ":"
-                | "_"
-                | UCASE_LETTER
-                | LCASE_LETTER
+IDENTIFIER_START: /\u003A/
+                | /\u005F/
+                | /[\u0041-\u005A]/
+                | /[\u0061-\u007A]/
                 | /[\u00C0-\u00D6]/
                 | /[\u00D8-\u00F6]/
                 | /[\u00F8-\u02FF]/
@@ -186,7 +225,7 @@ IDENTIFIER_START: ":"
                 | /[\u3001-\uD7FF]/
                 | /[\uF900-\uFDCF]/
                 | /[\uFDF0-\uFFFD]/
-                | /[\U00010000-\U0010FFFF]/
+                | /[\U00010000-\U000EFFFF]/
 
 // Property Name
 property_name: IDENTIFIER
@@ -218,20 +257,18 @@ _argument: character_clause
 // - Added support for `Z` in WKT which is not included in CQL2 spec.
 // - Added minimum of 2 points for LineString.
 // - Added linear ring with 4 points minimum for Polygons.
-// - Added a `geometry_instance` and restricted it to not inclue `bbox` or `geometry_collection`.
+// - Added a `geometry_instance` and restricted it to not inclue `bbox`.
 //     - The spec allows for BBOX inside GeometryCollection which does not seem to be valid WKT.
-//     - The spec allows for GeometryCollection within GeometryCollection but GeoJSON discourages it.
-//     - Will move `geometry_collection` back to `geometry_instance` when geojson-pydantic supports it.
 // - The spec does not support `EMPTY` WKT. May add it in the future.
 ?spatial_instance: geometry_instance
-                 | geometry_collection
                  | bbox
 ?geometry_instance: point
-                 | linestring
-                 | polygon
-                 | multi_point
-                 | multi_linestring
-                 | multi_polygon
+                  | linestring
+                  | polygon
+                  | multi_point
+                  | multi_linestring
+                  | multi_polygon
+                  | geometry_collection
 
 point: "POINT"i "Z"i? point_coordinates
 linestring: "LINESTRING"i "Z"i? linestring_coordinates
@@ -249,7 +286,10 @@ linestring_coordinates: "(" coordinate ("," coordinate)+ ")"
 // BNF does not include linear ring, but you cannot have a polygon without 4 points.
 linear_ring_coordinates: "(" coordinate "," coordinate "," coordinate ("," coordinate)+ ")"
 polygon_coordinates:  "(" linear_ring_coordinates ("," linear_ring_coordinates)* ")"
+// To maintain compatibility with other tools we will also accept `coordinate` here, though
+// the spec does not allow for it.
 multi_point_coordinates: "(" point_coordinates ("," point_coordinates)* ")"
+                       | "(" coordinate ("," coordinate)* ")"
 multi_linestring_coordinates: "(" linestring_coordinates ("," linestring_coordinates)* ")"
 multi_polygon_coordinates: "(" polygon_coordinates ("," polygon_coordinates)* ")"
 
@@ -274,24 +314,22 @@ DATE_TIME: DATE "T" TIME "Z"
 DOTDOT: "'..'"
 
 // General keywords which are used in a few places.
-_OR: "OR"i
-_AND: "AND"i
-_NOT: "NOT"i
+_OR.2: "OR"i
+_AND.2: "AND"i
+_NOT.2: "NOT"i
 
-_LIKE: "LIKE"i
-_BETWEEN: "BETWEEN"i
-_IN: "IN"i
-_IS: "IS"i
-_NULL: "NULL"i
+_LIKE.2: "LIKE"i
+_BETWEEN.2: "BETWEEN"i
+_IN.2: "IN"i
+_IS.2: "IS"i
+_NULL.2: "NULL"i
 
 // Need to increase priority to ensure it is not considered an IDENTIFIER
 BOOLEAN_LITERAL.2: "TRUE"i | "FALSE"i
 
-// Imports from common instead od re-defining
+// Import from common instead of re-defining
 %import common.DIGIT
 %import common.SIGNED_NUMBER
-%import common.LCASE_LETTER
-%import common.UCASE_LETTER
 
 // Ignore White Space
 %import common.WS
diff --git a/pycql2/cql2_pydantic.py b/pycql2/cql2_pydantic.py
index 205c408..eb52ea9 100644
--- a/pycql2/cql2_pydantic.py
+++ b/pycql2/cql2_pydantic.py
@@ -1,14 +1,11 @@
 from __future__ import annotations
 
 from datetime import date, datetime
-from enum import Enum
 from typing import (
-    TYPE_CHECKING,
     Any,
     List,
     Literal,
     MutableSequence,
-    Sequence,
     Tuple,
     Union,
 )
@@ -26,25 +23,13 @@
 )
 from typing_extensions import Annotated
 
-# The use of `Strict*` is necessary in a few places because pydantic will convert
-# booleans to numbers and vice versa. As well as various types to strings. This
-# should hopefully be fixed by Pydantic v2.
+from pycql2.utils import _join_list, _make_char_literal
 
 # Since we are using strict, we need to use a union to allow for ints and floats
 # in places we want to have numbers.
 StrictFloatOrInt = Union[StrictFloat, StrictInt]
 
 
-def make_char_literal(string: str) -> str:
-    # Escape any single quotes with an extra one.
-    # Monitor https://github.com/opengeospatial/ogcapi-features/issues/717
-    return "'" + string.replace("'", "''") + "'"
-
-
-def join_list(items: Sequence, sep: str) -> str:
-    return sep.join(str(item) for item in items)
-
-
 class NotExpression(BaseModel):
     op: Literal["not"]
     args: Tuple[BooleanExpression]
@@ -82,56 +67,58 @@ class IsInListPredicate(BaseModel):
     args: Tuple[ScalarExpression, List[ScalarExpression]]
 
     def __str__(self) -> str:
-        return f"{self.args[0]} IN ({join_list(self.args[1], ', ')})"
+        return f"{self.args[0]} IN ({_join_list(self.args[1], ', ')})"
 
 
 class IsNullPredicate(BaseModel):
     op: Literal["isNull"]
-    args: IsNullOperand
+    args: Tuple[IsNullOperand]
 
     def __str__(self) -> str:
-        return f"{self.args} IS NULL"
-
-
-class SpatialOperator(str, Enum):
-    s_contains = "s_contains"
-    s_crosses = "s_crosses"
-    s_disjoint = "s_disjoint"
-    s_equals = "s_equals"
-    s_intersects = "s_intersects"
-    s_overlaps = "s_overlaps"
-    s_touches = "s_touches"
-    s_within = "s_within"
+        return f"{self.args[0]} IS NULL"
+
+
+SpatialFunction = Literal[
+    "s_contains",
+    "s_crosses",
+    "s_disjoint",
+    "s_equals",
+    "s_intersects",
+    "s_overlaps",
+    "s_touches",
+    "s_within",
+]
 
 
 class SpatialPredicate(BaseModel):
-    op: SpatialOperator
+    op: SpatialFunction
     args: Tuple[GeomExpression, GeomExpression]
 
     def __str__(self) -> str:
         return f"{self.op.upper()}({self.args[0]}, {self.args[1]})"
 
 
-class TemporalOperator(str, Enum):
-    t_after = "t_after"
-    t_before = "t_before"
-    t_contains = "t_contains"
-    t_disjoint = "t_disjoint"
-    t_during = "t_during"
-    t_equals = "t_equals"
-    t_finishedBy = "t_finishedBy"
-    t_finishes = "t_finishes"
-    t_intersects = "t_intersects"
-    t_meets = "t_meets"
-    t_metBy = "t_metBy"
-    t_overlappedBy = "t_overlappedBy"
-    t_overlaps = "t_overlaps"
-    t_startedBy = "t_startedBy"
-    t_starts = "t_starts"
+TemporalFunction = Literal[
+    "t_after",
+    "t_before",
+    "t_contains",
+    "t_disjoint",
+    "t_during",
+    "t_equals",
+    "t_finishedBy",
+    "t_finishes",
+    "t_intersects",
+    "t_meets",
+    "t_metBy",
+    "t_overlappedBy",
+    "t_overlaps",
+    "t_startedBy",
+    "t_starts",
+]
 
 
 class TemporalPredicate(BaseModel):
-    op: TemporalOperator
+    op: TemporalFunction
     args: Tuple[TemporalExpression, TemporalExpression]
 
     def __str__(self) -> str:
@@ -142,7 +129,7 @@ class Array(RootModel):
     root: MutableSequence[ArrayElement]
 
     def __str__(self) -> str:
-        return f"({join_list(self.root, ', ')})"
+        return f"({_join_list(self.root, ', ')})"
 
 
 class ArrayExpression(RootModel):
@@ -152,15 +139,16 @@ def __str__(self) -> str:
         return f"({self.root[0]}, {self.root[1]})"
 
 
-class ArrayOperator(str, Enum):
-    a_containedBy = "a_containedBy"
-    a_contains = "a_contains"
-    a_equals = "a_equals"
-    a_overlaps = "a_overlaps"
+ArrayFunction = Literal[
+    "a_containedBy",
+    "a_contains",
+    "a_equals",
+    "a_overlaps",
+]
 
 
 class ArrayPredicate(BaseModel):
-    op: ArrayOperator
+    op: ArrayFunction
     args: ArrayExpression
 
     def __str__(self) -> str:
@@ -178,13 +166,7 @@ def __str__(self) -> str:
         return str(self.root)
 
 
-# Type checking does not like `conlist`, so use a conditional here to avoid the error.
-# Due to the recursive nature of the grammar we cannot use `Field` here because it
-# does not properly enforce min / max items when nested. May be fixed by pydantic2.
-if TYPE_CHECKING:
-    BooleanExpressionList = List[BooleanExpression]
-else:
-    BooleanExpressionList = Annotated[List[BooleanExpression], Field(min_length=2)]
+BooleanExpressionList = Annotated[List[BooleanExpression], Field(min_length=2)]
 
 
 class AndOrExpression(BaseModel):
@@ -193,11 +175,10 @@ class AndOrExpression(BaseModel):
 
     def __str__(self) -> str:
         # May result in excessive parens, but guarantees correctness.
-        return f"({join_list(self.args, f' {self.op.upper()} ')})"
+        return f"({_join_list(self.args, f' {self.op.upper()} ')})"
 
 
 class ArithmeticExpression(BaseModel):
-    # cql2-text defines two operators cql2-json does not.
     op: Literal["+", "-", "*", "/", "^", "%", "div"]
     args: Tuple[ArithmeticOperandsItems, ArithmeticOperandsItems]
 
@@ -212,7 +193,7 @@ class Function(BaseModel):
 
     def __str__(self) -> str:
         # If self.args, comma join them. Otherwise, empty string. Inside parens.
-        return self.name + "(" + (join_list(self.args, ", ") if self.args else "") + ")"
+        return f"{self.name}({_join_list(self.args, ', ') if self.args else ''})"
 
 
 class FunctionRef(BaseModel):
@@ -226,7 +207,7 @@ class PropertyRef(BaseModel):
     property: StrictStr
 
     def __str__(self) -> str:
-        # May not need to be quoted, but it can be, so its safer and easier
+        # The safest thing to do is always quote it.
         return f'"{self.property}"'
 
 
@@ -241,7 +222,7 @@ class TimestampInstant(BaseModel):
     timestamp: datetime
 
     def __str__(self) -> str:
-        # format it as iso with `Z` at the end. Note this will always include the
+        # Use ISO format with `Z` at the end. Note: this will always include the
         # microseconds, even if they are 0.
         return f"TIMESTAMP('{self.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ')}')"
 
@@ -253,6 +234,13 @@ def __str__(self) -> str:
         return f"BBOX{self.bbox}"
 
 
+class GeometryLiteral(RootModel):
+    root: Union[Geometry, GeometryCollection]
+
+    def __str__(self) -> str:
+        return self.root.wkt
+
+
 class IntervalArrayItems(RootModel):
     root: Union[datetime, date, Literal[".."], PropertyRef, FunctionRef]
 
@@ -277,59 +265,62 @@ def __str__(self) -> str:
         return f"INTERVAL({self.interval[0]}, {self.interval[1]})"
 
 
+# The easiest way to handle typing for `Casei` and `Accenti`, so far, has been
+# using a subclass for each `CharacterExpression` and `PatternExpression`.
+# Runtime typing on Generics has been problematic.
 class Casei(BaseModel):
-    casei: Union[CharacterExpression, PatternExpression]
+    op: Literal["casei"]
+    args: Tuple[Union[CharacterExpression, PatternExpression]]
 
     def __str__(self) -> str:
-        return f"CASEI({self.casei})"
+        return f"CASEI({self.args[0]})"
 
 
 class CaseiCharacterExpression(Casei):
-    casei: CharacterExpression
+    args: Tuple[CharacterExpression]
 
 
 class CaseiPatternExpression(Casei):
-    casei: PatternExpression
+    args: Tuple[PatternExpression]
 
 
 class Accenti(BaseModel):
-    accenti: Union[CharacterExpression, PatternExpression]
+    op: Literal["accenti"]
+    args: Tuple[Union[CharacterExpression, PatternExpression]]
 
     def __str__(self) -> str:
-        return f"ACCENTI({self.accenti})"
+        return f"ACCENTI({self.args[0]})"
 
 
 class AccentiCharacterExpression(Accenti):
-    accenti: CharacterExpression
+    args: Tuple[CharacterExpression]
 
 
 class AccentiPatternExpression(Accenti):
-    accenti: PatternExpression
+    args: Tuple[PatternExpression]
 
 
-class GeometryLiteral(RootModel):
-    root: Union[Geometry, GeometryCollection]
+class _CharLiteralRootModel(RootModel):
+    """Root Model used for anything which can contain a Character Literal.
 
-    def __str__(self) -> str:
-        return self.root.wkt
+    It will apply the quotes required while allowing it to stay a `str`.
+    """
 
-
-class CharLiteralRootModel(RootModel):
     root: Any
 
     def __str__(self) -> str:
-        # If it is already a string, make it a char literal
+        # If root is a string, quote it to make it a Character Literal.
         if isinstance(self.root, str):
-            return make_char_literal(self.root)
-        # Otherwise, return the string representation of the root
+            return _make_char_literal(self.root)
+        # Otherwise, return the string representation of the root.
         return str(self.root)
 
 
-class CharacterClause(CharLiteralRootModel):
+class CharacterClause(_CharLiteralRootModel):
     root: CharacterClauseItems
 
 
-class PatternExpression(CharLiteralRootModel):
+class PatternExpression(_CharLiteralRootModel):
     root: PatternExpressionItems
 
 
@@ -370,7 +361,7 @@ class PatternExpression(CharLiteralRootModel):
     FunctionRef,
 ]
 
-# Extra types to match the cql2-text grammar better
+# Extra types to match the cql2-text grammar better.
 IsNullOperand = Union[
     CharacterClause,
     NumericExpression,
@@ -406,6 +397,7 @@ class PatternExpression(CharLiteralRootModel):
     SpatialPredicate,
     TemporalPredicate,
     ArrayPredicate,
+    FunctionRef,
     StrictBool,
 ]
 
diff --git a/pycql2/cql2_transformer.py b/pycql2/cql2_transformer.py
index 5c13de9..6d3e695 100644
--- a/pycql2/cql2_transformer.py
+++ b/pycql2/cql2_transformer.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 from datetime import date, datetime, timezone
-from typing import Any, List, Union, cast
+from typing import Any, List, Literal, Union, cast
 
 from geojson_pydantic import (
     GeometryCollection,
@@ -14,6 +14,7 @@
 )
 from geojson_pydantic.geometries import Geometry
 from geojson_pydantic.types import (
+    BBox,
     LineStringCoords,
     MultiLineStringCoords,
     MultiPointCoords,
@@ -33,7 +34,7 @@
     ArrayElement,
     ArrayExpression,
     ArrayExpressionItems,
-    ArrayOperator,
+    ArrayFunction,
     ArrayPredicate,
     BboxLiteral,
     BinaryComparisonPredicate,
@@ -59,14 +60,15 @@
     PatternExpression,
     PropertyRef,
     ScalarExpression,
-    SpatialOperator,
+    SpatialFunction,
     SpatialPredicate,
     StrictFloatOrInt,
     TemporalExpression,
-    TemporalOperator,
+    TemporalFunction,
     TemporalPredicate,
     TimestampInstant,
 )
+from pycql2.utils import _clean_char_literal
 
 BooleanExpressionOrItems = Union[BooleanExpression, BooleanExpressionItems]
 Predicate = Union[
@@ -243,7 +245,7 @@ def not_in_list(
 
     @v_args(inline=True)
     def is_null(self, is_null_operand: IsNullOperand) -> IsNullPredicate:
-        return IsNullPredicate(op="isNull", args=is_null_operand)
+        return IsNullPredicate(op="isNull", args=(is_null_operand,))
 
     @v_args(inline=True)
     def is_not_null(self, is_null_operand: IsNullOperand) -> NotExpression:
@@ -251,7 +253,7 @@ def is_not_null(self, is_null_operand: IsNullOperand) -> NotExpression:
             op="not",
             args=(
                 BooleanExpression(
-                    root=IsNullPredicate(op="isNull", args=is_null_operand)
+                    root=IsNullPredicate(op="isNull", args=(is_null_operand,))
                 ),
             ),
         )
@@ -260,22 +262,22 @@ def is_not_null(self, is_null_operand: IsNullOperand) -> NotExpression:
 
     @v_args(inline=True)
     def spatial_predicate(
-        self, spatial_operator: str, e1: GeomExpression, e2: GeomExpression
+        self, spatial_function: str, e1: GeomExpression, e2: GeomExpression
     ) -> SpatialPredicate:
-        op = SpatialOperator(spatial_operator.lower())
+        op = cast(SpatialFunction, spatial_function.lower())
         return SpatialPredicate(op=op, args=(e1, e2))
 
     # Temporal Predicate
 
     @v_args(inline=True)
     def temporal_predicate(
-        self, temporal_operator: str, e1: TemporalExpression, e2: TemporalExpression
+        self, temporal_function: str, e1: TemporalExpression, e2: TemporalExpression
     ) -> TemporalPredicate:
-        op = temporal_operator.lower()
+        op = temporal_function.lower()
         # Special case for any operation that ends with `by`
         if op.endswith("by"):
             op = op[:-2] + "By"
-        op = TemporalOperator(op)
+        op = cast(TemporalFunction, op)
         return TemporalPredicate(op=op, args=(e1, e2))
 
     # Array Predicate
@@ -286,13 +288,13 @@ def array(self, *array_elements: ArrayElement) -> Array:
 
     @v_args(inline=True)
     def array_predicate(
-        self, array_operator: str, e1: ArrayExpressionItems, e2: ArrayExpressionItems
+        self, array_function: str, e1: ArrayExpressionItems, e2: ArrayExpressionItems
     ) -> ArrayPredicate:
-        op = array_operator.lower()
+        op = array_function.lower()
         # Special case for any operation that ends with `by`
         if op.endswith("by"):
             op = op[:-2] + "By"
-        op = ArrayOperator(op)
+        op = cast(ArrayFunction, op)
         return ArrayPredicate(op=op, args=ArrayExpression(root=(e1, e2)))
 
     # Arithmetic
@@ -355,8 +357,7 @@ def negative(
 
     @v_args(inline=True)
     def CHARACTER_LITERAL(self, characters: str) -> str:
-        # Strip the extra `'` off strings which are unneeded in json.
-        return characters.strip("'")
+        return _clean_char_literal(characters)
 
     argument_list = _passthrough
 
@@ -377,28 +378,28 @@ def casei_pattern(
         self,
         expression: PatternExpression,
     ) -> CaseiPatternExpression:
-        return CaseiPatternExpression(casei=expression)
+        return CaseiPatternExpression(op="casei", args=(expression,))
 
     @v_args(inline=True)
     def casei_character(
         self,
         expression: CharacterExpression,
     ) -> CaseiCharacterExpression:
-        return CaseiCharacterExpression(casei=expression)
+        return CaseiCharacterExpression(op="casei", args=(expression,))
 
     @v_args(inline=True)
     def accenti_pattern(
         self,
         expression: PatternExpression,
     ) -> AccentiPatternExpression:
-        return AccentiPatternExpression(accenti=expression)
+        return AccentiPatternExpression(op="accenti", args=(expression,))
 
     @v_args(inline=True)
     def accenti_character(
         self,
         expression: CharacterExpression,
     ) -> AccentiCharacterExpression:
-        return AccentiCharacterExpression(accenti=expression)
+        return AccentiCharacterExpression(op="accenti", args=(expression,))
 
     # Spatial Definitions
 
@@ -456,8 +457,9 @@ def geometry_collection(self, geometries: List[Geometry]) -> GeometryCollection:
 
     @v_args(inline=True)
     def bbox(self, *coordinates: float) -> BboxLiteral:
-        # We know that `coordinates` will always be 4 or 6, so we can just use the
-        # tuple directly.
+        # Based on the grammar, we know there will always be 4 or 6 `coordinates`.
+        # No additional validation is necessary.
+        coordinates = cast(BBox, coordinates)
         return BboxLiteral(bbox=coordinates)
 
     # Date and Time Definitions
@@ -482,9 +484,9 @@ def DATE_TIME(self, datetimestring: str) -> datetime:
         )
 
     @v_args(inline=True)
-    def DOTDOT(self, dotdot: str) -> str:
-        # Strip the `'` which aren't used in the json representation
-        return dotdot.strip("'")
+    def DOTDOT(self, _dotdot: Literal["'..'"]) -> str:
+        # The json representation does not include the single quotes.
+        return ".."
 
     @v_args(inline=True)
     def date_instant(self, date_: date) -> DateInstant:
diff --git a/pycql2/utils.py b/pycql2/utils.py
new file mode 100644
index 0000000..93261d9
--- /dev/null
+++ b/pycql2/utils.py
@@ -0,0 +1,30 @@
+from typing import Sequence
+
+QUOTE = "'"
+QUOTE_QUOTE = "''"
+BACKSLASH_QUOTE = r"\'"
+
+
+def _make_char_literal(characters: str) -> str:
+    """Converts string to a CQL2 Character Literal.
+
+    Adds single quotes `'` around string.
+    Escapes any single quotes with backslash `\'`.
+    """
+    return f"'{characters.replace(QUOTE, BACKSLASH_QUOTE)}'"
+
+
+def _clean_char_literal(characters: str) -> str:
+    """Cleans CQL2 Character Literal.
+
+    Removes outer single quotes `'`.
+    Removes escaping from single quotes `''` or `\'`
+
+    Based on the grammar, we know the first and last characters are single quotes `'`.
+    """
+    return characters[1:-1].replace(QUOTE_QUOTE, QUOTE).replace(BACKSLASH_QUOTE, QUOTE)
+
+
+def _join_list(items: Sequence, sep: str) -> str:
+    """Join string of each item with sep."""
+    return sep.join(str(item) for item in items)
diff --git a/pyproject.toml b/pyproject.toml
index ca747f7..52cf1cc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,4 @@
+# Poetry
 [tool.poetry]
 name = "pycql2"
 version = "0.2.0"
@@ -29,10 +30,11 @@ pydantic = "^2.0.0"
 geojson-pydantic = "^1.0.1"
 lark = "^1.0.0"
 
-pytest = { version = ">=7.2", optional = true }
+pytest = { version = ">=7.2,<8.0", optional = true }
 coverage = { version = ">=7.2", optional = true, extras = ["toml"] }
 pytest-cov = { version = ">=4.0", optional = true }
-hypothesis = { version = ">=6.91.0", optional = true }
+hypothesis = { version = ">=6.94.0", optional = true }
+hypofuzz = { version = ">=24.02.1", optional = true }
 black = { version = ">=23.1.0", optional = true }
 ruff = { version = ">=0.0.254", optional = true }
 mypy = { version = ">=1.1.0", optional = true }
@@ -41,14 +43,17 @@ pre-commit = { version = ">=3.1.1", optional = true, python = ">=3.9" }
 [tool.poetry.extras]
 dev = ["black", "ruff", "mypy", "pre-commit"]
 test = ["pytest", "coverage", "pytest-cov", "hypothesis"]
+fuzz = ["hypofuzz"]
 
 [build-system]
 requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
 
-
+# Ruff
 [tool.ruff]
 target-version = "py38"
+
+[tool.ruff.lint]
 select = [
     "F",   # pyflakes
     "E",   # pycodestyle errors
@@ -62,39 +67,61 @@ select = [
     "RSE", # flake8-raise
     "RET", # flake8-return
     "SIM", # flake8-simplify
-    # "PTH", # flake8-use-pathlib
+    "PTH", # flake8-use-pathlib
     "TRY", # tryceratops
     "RUF", # Ruff specific rules
 ]
-fix = true
 ignore = [
-    "E501", # Black handles line length
+    "E501", # Line length is handled automatically
 ]
 
-[tool.ruff.isort]
+[tool.ruff.lint.isort]
 combine-as-imports = true
 
-[tool.ruff.per-file-ignores]
+[tool.ruff.lint.per-file-ignores]
 "test_*" = [
     "S101", # Use of assert is fine in tests
 ]
 
+# Pytest
+[tool.pytest.ini_options]
+pythonpath = "pycql2"
+testpaths = ["tests"]
+# Most of the tests compare highly nested pydantic models and are truncated
+# without extra verbosity.
+addopts = "-vv"
+
+# Coverage
 [tool.coverage.run]
 source = ["pycql2"]
 branch = true
 
 [tool.coverage.report]
-exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:"]
+show_missing = true
+skip_empty = true
 
+# MyPy
 [tool.mypy]
+mypy_path = "pycql2"
 python_version = "3.10"
 plugins = ["pydantic.mypy"]
 show_error_codes = true
 check_untyped_defs = true
+namespace_packages = true
 no_implicit_reexport = true
+no_implicit_optional = true
 disallow_incomplete_defs = true
 disallow_untyped_calls = true
 disallow_untyped_defs = true
 warn_unused_configs = true
 warn_unused_ignores = true
 warn_redundant_casts = true
+
+[[tool.mypy.overrides]]
+module = ["rich"]
+ignore_missing_imports = true
+
+[tool.pydantic-mypy]
+init_forbid_extra = true
+init_typed = true
+warn_required_dynamic_aliases = true


Test patch:
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 1d250ab..bce930e 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -2,50 +2,42 @@ name: Tests
 
 on:
   push:
-    branches: [main]
-    tags: ["v*"]
+    branches:
+      - main
+      - develop
   pull_request:
-    branches: [main]
+    branches:
+      - main
+      - develop
+  workflow_call:
 
 jobs:
   tests:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+        python-version:
+          - "3.8"
+          - "3.9"
+          - "3.10"
+          - "3.11"
+          - "3.12"
 
     steps:
-      - uses: actions/checkout@v4
+      - name: Checkout the repository
+        uses: actions/checkout@v4
+
       - name: Install poetry
         run: pipx install poetry
+
       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
-          cache: "poetry"
+          cache: poetry
+
       - name: Install dependencies
         run: poetry install -E test
-      - name: Run tests with coverage
-        run: poetry run pytest --ignore=tests/test_pycql2_hypothesis.py --cov=pycql2 --cov-report=xml tests/
 
-  publish:
-    needs: [tests]
-    runs-on: ubuntu-latest
-    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
-    steps:
-      - uses: actions/checkout@v4
-      - name: Install poetry
-        run: pipx install poetry
-      - name: Set up Python 3.12
-        uses: actions/setup-python@v5
-        with:
-          python-version: 3.12
-          cache: "poetry"
-      - name: Install dependencies
-        run: poetry install --no-dev
-      - name: Build package
-        run: poetry build
-      - name: Publish package
-        uses: pypa/gh-action-pypi-publish@release/v1
-        with:
-          password: ${{ secrets.PYPI_API_TOKEN }}
+      - name: Run tests with coverage
+        run: poetry run pytest --ignore=tests/test_pycql2_hypothesis.py --cov=pycql2 --cov-report=term-missing tests/
diff --git a/tests/data/json/filter01.json b/tests/data/json/filter01.json
index 2ff748d..d9d9e5f 100644
--- a/tests/data/json/filter01.json
+++ b/tests/data/json/filter01.json
@@ -1,9 +1,4 @@
 {
   "op": "=",
-  "args": [
-    {
-      "property": "id"
-    },
-    "fa7e1920-9107-422d-a3db-c468cbc5d6df"
-  ]
+  "args": [{ "property": "id" }, "fa7e1920-9107-422d-a3db-c468cbc5d6df"]
 }
diff --git a/tests/data/json/filter02.json b/tests/data/json/filter02.json
index 127b202..e6ba023 100644
--- a/tests/data/json/filter02.json
+++ b/tests/data/json/filter02.json
@@ -1,9 +1,4 @@
 {
   "op": "<>",
-  "args": [
-    {
-      "property": "id"
-    },
-    "fa7e1920-9107-422d-a3db-c468cbc5d6df"
-  ]
+  "args": [{ "property": "id" }, "fa7e1920-9107-422d-a3db-c468cbc5d6df"]
 }
diff --git a/tests/data/json/filter03.json b/tests/data/json/filter03.json
index 6a2852c..c013169 100644
--- a/tests/data/json/filter03.json
+++ b/tests/data/json/filter03.json
@@ -1,9 +1,4 @@
 {
   "op": "<",
-  "args": [
-    {
-      "property": "value"
-    },
-    10
-  ]
+  "args": [{ "property": "value" }, 10]
 }
diff --git a/tests/data/json/filter04.json b/tests/data/json/filter04.json
index d16afc4..b51e7d1 100644
--- a/tests/data/json/filter04.json
+++ b/tests/data/json/filter04.json
@@ -1,9 +1,4 @@
 {
   "op": ">",
-  "args": [
-    {
-      "property": "value"
-    },
-    10
-  ]
+  "args": [{ "property": "value" }, 10]
 }
diff --git a/tests/data/json/filter05.json b/tests/data/json/filter05.json
index fbd3c05..7c60165 100644
--- a/tests/data/json/filter05.json
+++ b/tests/data/json/filter05.json
@@ -1,9 +1,4 @@
 {
   "op": "<=",
-  "args": [
-    {
-      "property": "value"
-    },
-    10
-  ]
+  "args": [{ "property": "value" }, 10]
 }
diff --git a/tests/data/json/filter06.json b/tests/data/json/filter06.json
index 2e1bdf2..eb1084a 100644
--- a/tests/data/json/filter06.json
+++ b/tests/data/json/filter06.json
@@ -1,9 +1,4 @@
 {
   "op": ">=",
-  "args": [
-    {
-      "property": "value"
-    },
-    10
-  ]
+  "args": [{ "property": "value" }, 10]
 }
diff --git a/tests/data/json/filter07.json b/tests/data/json/filter07.json
index bd8cebe..c33ebe8 100644
--- a/tests/data/json/filter07.json
+++ b/tests/data/json/filter07.json
@@ -1,9 +1,4 @@
 {
   "op": "like",
-  "args": [
-    {
-      "property": "name"
-    },
-    "foo%"
-  ]
+  "args": [{ "property": "name" }, "foo%"]
 }
diff --git a/tests/data/json/filter08.json b/tests/data/json/filter08.json
index 583bc86..22958da 100644
--- a/tests/data/json/filter08.json
+++ b/tests/data/json/filter08.json
@@ -3,12 +3,7 @@
   "args": [
     {
       "op": "like",
-      "args": [
-        {
-          "property": "name"
-        },
-        "foo%"
-      ]
+      "args": [{ "property": "name" }, "foo%"]
     }
   ]
 }
diff --git a/tests/data/json/filter09.json b/tests/data/json/filter09.json
index ed2850d..7a67d62 100644
--- a/tests/data/json/filter09.json
+++ b/tests/data/json/filter09.json
@@ -1,10 +1,4 @@
 {
   "op": "between",
-  "args": [
-    {
-      "property": "value"
-    },
-    10,
-    20
-  ]
+  "args": [{ "property": "value" }, 10, 20]
 }
diff --git a/tests/data/json/filter10.json b/tests/data/json/filter10.json
index d0289d9..57d4590 100644
--- a/tests/data/json/filter10.json
+++ b/tests/data/json/filter10.json
@@ -3,13 +3,7 @@
   "args": [
     {
       "op": "between",
-      "args": [
-        {
-          "property": "value"
-        },
-        10,
-        20
-      ]
+      "args": [{ "property": "value" }, 10, 20]
     }
   ]
 }
diff --git a/tests/data/json/filter11.json b/tests/data/json/filter11.json
index 170d00d..2650d76 100644
--- a/tests/data/json/filter11.json
+++ b/tests/data/json/filter11.json
@@ -1,13 +1,4 @@
 {
   "op": "in",
-  "args": [
-    {
-      "property": "value"
-    },
-    [
-      1.0,
-      2.0,
-      3.0
-    ]
-  ]
+  "args": [{ "property": "value" }, [1.0, 2.0, 3.0]]
 }
diff --git a/tests/data/json/filter12.json b/tests/data/json/filter12.json
index cbcdf9b..3b92fd8 100644
--- a/tests/data/json/filter12.json
+++ b/tests/data/json/filter12.json
@@ -3,16 +3,7 @@
   "args": [
     {
       "op": "in",
-      "args": [
-        {
-          "property": "value"
-        },
-        [
-          "a",
-          "b",
-          "c"
-        ]
-      ]
+      "args": [{ "property": "value" }, ["a", "b", "c"]]
     }
   ]
 }
diff --git a/tests/data/json/filter13.json b/tests/data/json/filter13.json
index 2170723..8c93015 100644
--- a/tests/data/json/filter13.json
+++ b/tests/data/json/filter13.json
@@ -1,6 +1,4 @@
 {
   "op": "isNull",
-  "args": {
-    "property": "value"
-  }
+  "args": [{ "property": "value" }]
 }
diff --git a/tests/data/json/filter14.json b/tests/data/json/filter14.json
index ef9b50b..24a8ac3 100644
--- a/tests/data/json/filter14.json
+++ b/tests/data/json/filter14.json
@@ -3,9 +3,7 @@
   "args": [
     {
       "op": "isNull",
-      "args": {
-        "property": "value"
-      }
+      "args": [{ "property": "value" }]
     }
   ]
 }
diff --git a/tests/data/json/filter15.json b/tests/data/json/filter15.json
index ff541df..10fdabf 100644
--- a/tests/data/json/filter15.json
+++ b/tests/data/json/filter15.json
@@ -6,23 +6,13 @@
       "args": [
         {
           "op": "like",
-          "args": [
-            {
-              "property": "name"
-            },
-            "foo%"
-          ]
+          "args": [{ "property": "name" }, "foo%"]
         }
       ]
     },
     {
       "op": ">",
-      "args": [
-        {
-          "property": "value"
-        },
-        10
-      ]
+      "args": [{ "property": "value" }, 10]
     }
   ]
 }
diff --git a/tests/data/json/filter16.json b/tests/data/json/filter16.json
index 7f95868..2c493db 100644
--- a/tests/data/json/filter16.json
+++ b/tests/data/json/filter16.json
@@ -3,19 +3,11 @@
   "args": [
     {
       "op": "isNull",
-      "args": {
-        "property": "value"
-      }
+      "args": [{ "property": "value" }]
     },
     {
       "op": "between",
-      "args": [
-        {
-          "property": "value"
-        },
-        10,
-        20
-      ]
+      "args": [{ "property": "value" }, 10, 20]
     }
   ]
 }
diff --git a/tests/data/json/filter17.json b/tests/data/json/filter17.json
index 0393eea..04a8f47 100644
--- a/tests/data/json/filter17.json
+++ b/tests/data/json/filter17.json
@@ -1,18 +1,7 @@
 {
   "op": "s_intersects",
   "args": [
-    {
-      "property": "geometry"
-    },
-    {
-      "bbox": [
-        -128.098193,
-        -1.1,
-        -99999.0,
-        180.0,
-        90.0,
-        100000.0
-      ]
-    }
+    { "property": "geometry" },
+    { "bbox": [-128.098193, -1.1, -99999.0, 180.0, 90.0, 100000.0] }
   ]
 }
diff --git a/tests/data/json/filter18.json b/tests/data/json/filter18.json
index 74fda1f..f600d16 100644
--- a/tests/data/json/filter18.json
+++ b/tests/data/json/filter18.json
@@ -5,39 +5,16 @@
       "type": "Polygon",
       "coordinates": [
         [
-          [
-            -0.333333,
-            89.0
-          ],
-          [
-            -102.723546,
-            -0.5
-          ],
-          [
-            -179.0,
-            -89.0
-          ],
-          [
-            -1.9,
-            89.0
-          ],
-          [
-            -0.0,
-            89.0
-          ],
-          [
-            2.00001,
-            -1.9
-          ],
-          [
-            -0.333333,
-            89.0
-          ]
+          [-0.333333, 89.0],
+          [-102.723546, -0.5],
+          [-179.0, -89.0],
+          [-1.9, 89.0],
+          [-0.0, 89.0],
+          [2.00001, -1.9],
+          [-0.333333, 89.0]
         ]
       ]
     },
-    {
-      "property": "geometry"
-    }
+    { "property": "geometry" }
   ]
 }
diff --git a/tests/data/json/filter19.json b/tests/data/json/filter19.json
index 0e9d344..a6e0e06 100644
--- a/tests/data/json/filter19.json
+++ b/tests/data/json/filter19.json
@@ -1,30 +1,16 @@
 {
   "op": "s_disjoint",
   "args": [
-    {
-      "property": "geometry"
-    },
+    { "property": "geometry" },
     {
       "type": "MultiPolygon",
       "coordinates": [
         [
           [
-            [
-              144.022387,
-              45.176126
-            ],
-            [
-              -1.1,
-              0.0
-            ],
-            [
-              180.0,
-              47.808086
-            ],
-            [
-              144.022387,
-              45.176126
-            ]
+            [144.022387, 45.176126],
+            [-1.1, 0.0],
+            [180.0, 47.808086],
+            [144.022387, 45.176126]
           ]
         ]
       ]
diff --git a/tests/data/json/filter20.json b/tests/data/json/filter20.json
index 40e5845..94349be 100644
--- a/tests/data/json/filter20.json
+++ b/tests/data/json/filter20.json
@@ -1,43 +1,20 @@
 {
   "op": "s_touches",
   "args": [
-    {
-      "property": "geometry"
-    },
+    { "property": "geometry" },
     {
       "type": "MultiLineString",
       "coordinates": [
         [
-          [
-            -1.9,
-            -0.99999
-          ],
-          [
-            75.292574,
-            1.5
-          ],
-          [
-            -0.5,
-            -4.016458
-          ],
-          [
-            -31.708594,
-            -74.743801
-          ],
-          [
-            179.0,
-            -90.0
-          ]
+          [-1.9, -0.99999],
+          [75.292574, 1.5],
+          [-0.5, -4.016458],
+          [-31.708594, -74.743801],
+          [179.0, -90.0]
         ],
         [
-          [
-            -1.9,
-            -1.1
-          ],
-          [
-            1.5,
-            8.547371
-          ]
+          [-1.9, -1.1],
+          [1.5, 8.547371]
         ]
       ]
     }
diff --git a/tests/data/json/filter21.json b/tests/data/json/filter21.json
index dfacdff..631aee8 100644
--- a/tests/data/json/filter21.json
+++ b/tests/data/json/filter21.json
@@ -5,53 +5,19 @@
       "type": "Polygon",
       "coordinates": [
         [
-          [
-            -49.88024,
-            0.5,
-            -75993.341684
-          ],
-          [
-            -1.5,
-            -0.99999,
-            -100000.0
-          ],
-          [
-            0.0,
-            0.5,
-            -0.333333
-          ],
-          [
-            -49.88024,
-            0.5,
-            -75993.341684
-          ]
+          [-49.88024, 0.5, -75993.341684],
+          [-1.5, -0.99999, -100000.0],
+          [0.0, 0.5, -0.333333],
+          [-49.88024, 0.5, -75993.341684]
         ],
         [
-          [
-            -65.887123,
-            2.00001,
-            -100000.0
-          ],
-          [
-            0.333333,
-            -53.017711,
-            -79471.332949
-          ],
-          [
-            180.0,
-            0.0,
-            1852.616704
-          ],
-          [
-            -65.887123,
-            2.00001,
-            -100000.0
-          ]
+          [-65.887123, 2.00001, -100000.0],
+          [0.333333, -53.017711, -79471.332949],
+          [180.0, 0.0, 1852.616704],
+          [-65.887123, 2.00001, -100000.0]
         ]
       ]
     },
-    {
-      "property": "geometry"
-    }
+    { "property": "geometry" }
   ]
 }
diff --git a/tests/data/json/filter22.json b/tests/data/json/filter22.json
index 20c7ff7..88e108f 100644
--- a/tests/data/json/filter22.json
+++ b/tests/data/json/filter22.json
@@ -1,16 +1,7 @@
 {
   "op": "s_overlaps",
   "args": [
-    {
-      "property": "geometry"
-    },
-    {
-      "bbox": [
-        -179.912109,
-        1.9,
-        180.0,
-        16.897016
-      ]
-    }
+    { "property": "geometry" },
+    { "bbox": [-179.912109, 1.9, 180.0, 16.897016] }
   ]
 }
diff --git a/tests/data/json/filter23.json b/tests/data/json/filter23.json
index 3108e94..38b7a86 100644
--- a/tests/data/json/filter23.json
+++ b/tests/data/json/filter23.json
@@ -1,44 +1,18 @@
 {
   "op": "s_crosses",
   "args": [
-    {
-      "property": "geometry"
-    },
+    { "property": "geometry" },
     {
       "type": "LineString",
       "coordinates": [
-        [
-          172.03086,
-          1.5
-        ],
-        [
-          1.1,
-          -90.0
-        ],
-        [
-          -159.757695,
-          0.99999
-        ],
-        [
-          -180.0,
-          0.5
-        ],
-        [
-          -12.111235,
-          81.336403
-        ],
-        [
-          -0.5,
-          64.43958
-        ],
-        [
-          0.0,
-          81.991815
-        ],
-        [
-          -155.93831,
-          90.0
-        ]
+        [172.03086, 1.5],
+        [1.1, -90.0],
+        [-159.757695, 0.99999],
+        [-180.0, 0.5],
+        [-12.111235, 81.336403],
+        [-0.5, 64.43958],
+        [0.0, 81.991815],
+        [-155.93831, 90.0]
       ]
     }
   ]
diff --git a/tests/data/json/filter24.json b/tests/data/json/filter24.json
index 93a76f5..9589000 100644
--- a/tests/data/json/filter24.json
+++ b/tests/data/json/filter24.json
@@ -1,15 +1,10 @@
 {
   "op": "s_contains",
   "args": [
-    {
-      "property": "geometry"
-    },
+    { "property": "geometry" },
     {
       "type": "Point",
-      "coordinates": [
-        -3.508362,
-        -1.754181
-      ]
+      "coordinates": [-3.508362, -1.754181]
     }
   ]
 }
diff --git a/tests/data/json/filter25.json b/tests/data/json/filter25.json
index 5557ff7..6f5c790 100644
--- a/tests/data/json/filter25.json
+++ b/tests/data/json/filter25.json
@@ -1,11 +1,4 @@
 {
   "op": "t_after",
-  "args": [
-    {
-      "property": "updated_at"
-    },
-    {
-      "date": "2010-02-10"
-    }
-  ]
+  "args": [{ "property": "updated_at" }, { "date": "2010-02-10" }]
 }
diff --git a/tests/data/json/filter26.json b/tests/data/json/filter26.json
index ff0a897..df7842a 100644
--- a/tests/data/json/filter26.json
+++ b/tests/data/json/filter26.json
@@ -1,11 +1,7 @@
 {
   "op": "t_before",
   "args": [
-    {
-      "property": "updated_at"
-    },
-    {
-      "timestamp": "2012-08-10T05:30:00Z"
-    }
+    { "property": "updated_at" },
+    { "timestamp": "2012-08-10T05:30:00Z" }
   ]
 }
diff --git a/tests/data/json/filter27.json b/tests/data/json/filter27.json
index 8a080b5..163b7a6 100644
--- a/tests/data/json/filter27.json
+++ b/tests/data/json/filter27.json
@@ -2,13 +2,8 @@
   "op": "t_contains",
   "args": [
     {
-      "interval": [
-        "2000-01-01T00:00:00Z",
-        "2005-01-10T01:01:01.393216Z"
-      ]
+      "interval": ["2000-01-01T00:00:00Z", "2005-01-10T01:01:01.393216Z"]
     },
-    {
-      "property": "updated_at"
-    }
+    { "property": "updated_at" }
   ]
 }
diff --git a/tests/data/json/filter28.json b/tests/data/json/filter28.json
index 177bde1..2f93734 100644
--- a/tests/data/json/filter28.json
+++ b/tests/data/json/filter28.json
@@ -1,14 +1,7 @@
 {
   "op": "t_disjoint",
   "args": [
-    {
-      "interval": [
-        "..",
-        "2005-01-10T01:01:01.393216Z"
-      ]
-    },
-    {
-      "property": "coverage_date"
-    }
+    { "interval": ["..", "2005-01-10T01:01:01.393216Z"] },
+    { "property": "coverage_date" }
   ]
 }
diff --git a/tests/data/json/filter29.json b/tests/data/json/filter29.json
index 3abe28e..5eb0ab1 100644
--- a/tests/data/json/filter29.json
+++ b/tests/data/json/filter29.json
@@ -2,20 +2,8 @@
   "op": "t_during",
   "args": [
     {
-      "interval": [
-        {
-          "property": "created_at"
-        },
-        {
-          "property": "updated_at"
-        }
-      ]
+      "interval": [{ "property": "created_at" }, { "property": "updated_at" }]
     },
-    {
-      "interval": [
-        "2005-01-10",
-        "2010-02-10"
-      ]
-    }
+    { "interval": ["2005-01-10", "2010-02-10"] }
   ]
 }
diff --git a/tests/data/json/filter30.json b/tests/data/json/filter30.json
index 5e8773d..fca5ed8 100644
--- a/tests/data/json/filter30.json
+++ b/tests/data/json/filter30.json
@@ -1,11 +1,4 @@
 {
   "op": "t_equals",
-  "args": [
-    {
-      "property": "updated_at"
-    },
-    {
-      "date": "1851-04-29"
-    }
-  ]
+  "args": [{ "property": "updated_at" }, { "date": "1851-04-29" }]
 }
diff --git a/tests/data/json/filter31.json b/tests/data/json/filter31.json
index c6da8d3..c348332 100644
--- a/tests/data/json/filter31.json
+++ b/tests/data/json/filter31.json
@@ -1,14 +1,9 @@
 {
   "op": "t_finishedBy",
   "args": [
+    { "property": "coverage_date" },
     {
-      "property": "coverage_date"
-    },
-    {
-      "interval": [
-        "1991-10-07T08:21:06.393262Z",
-        "2010-02-10T05:29:20.073225Z"
-      ]
+      "interval": ["1991-10-07T08:21:06.393262Z", "2010-02-10T05:29:20.073225Z"]
     }
   ]
 }
diff --git a/tests/data/json/filter32.json b/tests/data/json/filter32.json
index 3fdd754..7684d5f 100644
--- a/tests/data/json/filter32.json
+++ b/tests/data/json/filter32.json
@@ -1,14 +1,9 @@
 {
   "op": "t_finishes",
   "args": [
+    { "property": "coverage_dates" },
     {
-      "property": "coverage_dates"
-    },
-    {
-      "interval": [
-        "1991-10-07",
-        "2010-02-10T05:29:20.073225Z"
-      ]
+      "interval": ["1991-10-07", "2010-02-10T05:29:20.073225Z"]
     }
   ]
 }
diff --git a/tests/data/json/filter33.json b/tests/data/json/filter33.json
index f099f27..491259a 100644
--- a/tests/data/json/filter33.json
+++ b/tests/data/json/filter33.json
@@ -1,14 +1,9 @@
 {
   "op": "t_intersects",
   "args": [
+    { "property": "coverage_date" },
     {
-      "property": "coverage_date"
-    },
-    {
-      "interval": [
-        "1991-10-07T08:21:06.393262Z",
-        "2010-02-10T05:29:20.073225Z"
-      ]
+      "interval": ["1991-10-07T08:21:06.393262Z", "2010-02-10T05:29:20.073225Z"]
     }
   ]
 }
diff --git a/tests/data/json/filter34.json b/tests/data/json/filter34.json
index a2740b0..3cf237d 100644
--- a/tests/data/json/filter34.json
+++ b/tests/data/json/filter34.json
@@ -1,14 +1,7 @@
 {
   "op": "t_meets",
   "args": [
-    {
-      "interval": [
-        "2005-01-10",
-        "2010-02-10"
-      ]
-    },
-    {
-      "property": "coverage_dates"
-    }
+    { "interval": ["2005-01-10", "2010-02-10"] },
+    { "property": "coverage_dates" }
   ]
 }
diff --git a/tests/data/json/filter35.json b/tests/data/json/filter35.json
index cfaca15..244531e 100644
--- a/tests/data/json/filter35.json
+++ b/tests/data/json/filter35.json
@@ -2,13 +2,8 @@
   "op": "t_metBy",
   "args": [
     {
-      "interval": [
-        "2010-02-10T05:29:20.073225Z",
-        "2010-10-07"
-      ]
+      "interval": ["2010-02-10T05:29:20.073225Z", "2010-10-07"]
     },
-    {
-      "property": "coverage_dates"
-    }
+    { "property": "coverage_dates" }
   ]
 }
diff --git a/tests/data/json/filter36.json b/tests/data/json/filter36.json
index da40740..dff8485 100644
--- a/tests/data/json/filter36.json
+++ b/tests/data/json/filter36.json
@@ -2,13 +2,8 @@
   "op": "t_overlappedBy",
   "args": [
     {
-      "interval": [
-        "1991-10-07T08:21:06.393262Z",
-        "2010-02-10T05:29:20.073225Z"
-      ]
+      "interval": ["1991-10-07T08:21:06.393262Z", "2010-02-10T05:29:20.073225Z"]
     },
-    {
-      "property": "coverage_dates"
-    }
+    { "property": "coverage_dates" }
   ]
 }
diff --git a/tests/data/json/filter37.json b/tests/data/json/filter37.json
index f204119..645e88f 100644
--- a/tests/data/json/filter37.json
+++ b/tests/data/json/filter37.json
@@ -1,14 +1,9 @@
 {
   "op": "t_overlaps",
   "args": [
+    { "property": "coverage_date" },
     {
-      "property": "coverage_date"
-    },
-    {
-      "interval": [
-        "1991-10-07T08:21:06.393262Z",
-        "1992-10-09T08:08:08.393473Z"
-      ]
+      "interval": ["1991-10-07T08:21:06.393262Z", "1992-10-09T08:08:08.393473Z"]
     }
   ]
 }
diff --git a/tests/data/json/filter38.json b/tests/data/json/filter38.json
index cbb0a21..d4f6a0b 100644
--- a/tests/data/json/filter38.json
+++ b/tests/data/json/filter38.json
@@ -2,13 +2,8 @@
   "op": "t_startedBy",
   "args": [
     {
-      "interval": [
-        "1991-10-07T08:21:06.393262Z",
-        "2010-02-10T05:29:20.073225Z"
-      ]
+      "interval": ["1991-10-07T08:21:06.393262Z", "2010-02-10T05:29:20.073225Z"]
     },
-    {
-      "property": "coverage_dates"
-    }
+    { "property": "coverage_dates" }
   ]
 }
diff --git a/tests/data/json/filter39.json b/tests/data/json/filter39.json
index 122b0f2..b7ba657 100644
--- a/tests/data/json/filter39.json
+++ b/tests/data/json/filter39.json
@@ -1,14 +1,7 @@
 {
   "op": "t_starts",
   "args": [
-    {
-      "property": "coverage_dates"
-    },
-    {
-      "interval": [
-        "1991-10-07T08:21:06.393262Z",
-        ".."
-      ]
-    }
+    { "property": "coverage_dates" },
+    { "interval": ["1991-10-07T08:21:06.393262Z", ".."] }
   ]
 }
diff --git a/tests/data/json/filter40.json b/tests/data/json/filter40.json
index 7379f65..76748a1 100644
--- a/tests/data/json/filter40.json
+++ b/tests/data/json/filter40.json
@@ -4,11 +4,7 @@
     {
       "function": {
         "name": "Foo",
-        "args": [
-          {
-            "property": "geometry"
-          }
-        ]
+        "args": [{ "property": "geometry" }]
       }
     },
     true
diff --git a/tests/data/json/filter41.json b/tests/data/json/filter41.json
index 6e31e80..6bfad7e 100644
--- a/tests/data/json/filter41.json
+++ b/tests/data/json/filter41.json
@@ -5,15 +5,7 @@
     {
       "function": {
         "name": "Bar",
-        "args": [
-          {
-            "property": "geometry"
-          },
-          100,
-          "a",
-          "b",
-          false
-        ]
+        "args": [{ "property": "geometry" }, 100, "a", "b", false]
       }
     }
   ]
diff --git a/tests/data/json/filter42.json b/tests/data/json/filter42.json
index ffcb815..45f8733 100644
--- a/tests/data/json/filter42.json
+++ b/tests/data/json/filter42.json
@@ -1,13 +1,7 @@
 {
   "op": "=",
   "args": [
-    {
-      "accenti": {
-        "property": "owner"
-      }
-    },
-    {
-      "accenti": "Beyoncé"
-    }
+    { "op": "accenti", "args": [{ "property": "owner" }] },
+    { "op": "accenti", "args": ["Beyoncé"] }
   ]
 }
diff --git a/tests/data/json/filter43.json b/tests/data/json/filter43.json
index 64101a6..1a4ba2a 100644
--- a/tests/data/json/filter43.json
+++ b/tests/data/json/filter43.json
@@ -1,13 +1,7 @@
 {
   "op": "=",
   "args": [
-    {
-      "casei": {
-        "property": "owner"
-      }
-    },
-    {
-      "casei": "somebody else"
-    }
+    { "op": "casei", "args": [{ "property": "owner" }] },
+    { "op": "casei", "args": ["somebody else"] }
   ]
 }
diff --git a/tests/data/json/filter44.json b/tests/data/json/filter44.json
index 272bf33..527ed9d 100644
--- a/tests/data/json/filter44.json
+++ b/tests/data/json/filter44.json
@@ -1,13 +1,10 @@
 {
   "op": ">",
   "args": [
-    {"property":"value"},
+    { "property": "value" },
     {
       "op": "+",
-      "args": [
-        {"property":"foo"},
-        10
-      ]
+      "args": [{ "property": "foo" }, 10]
     }
   ]
 }
diff --git a/tests/data/json/filter45.json b/tests/data/json/filter45.json
index 733b9b0..b3aea73 100644
--- a/tests/data/json/filter45.json
+++ b/tests/data/json/filter45.json
@@ -1,13 +1,10 @@
 {
   "op": "<",
   "args": [
-    {"property":"value"},
+    { "property": "value" },
     {
       "op": "-",
-      "args": [
-        {"property":"foo"},
-        10
-      ]
+      "args": [{ "property": "foo" }, 10]
     }
   ]
 }
diff --git a/tests/data/json/filter46.json b/tests/data/json/filter46.json
index 387d66e..bc6f12d 100644
--- a/tests/data/json/filter46.json
+++ b/tests/data/json/filter46.json
@@ -1,13 +1,10 @@
 {
   "op": "<>",
   "args": [
-    {"property":"value"},
+    { "property": "value" },
     {
       "op": "*",
-      "args": [
-        22.1,
-        {"property":"foo"}
-      ]
+      "args": [22.1, { "property": "foo" }]
     }
   ]
 }
diff --git a/tests/data/json/filter47.json b/tests/data/json/filter47.json
index 08c47dd..a6981bc 100644
--- a/tests/data/json/filter47.json
+++ b/tests/data/json/filter47.json
@@ -1,13 +1,10 @@
 {
   "op": "=",
   "args": [
-    {"property":"value"},
+    { "property": "value" },
     {
       "op": "/",
-      "args": [
-        2,
-        {"property":"foo"}
-      ]
+      "args": [2, { "property": "foo" }]
     }
   ]
 }
diff --git a/tests/data/json/filter48.json b/tests/data/json/filter48.json
index 2a0b94c..f8b8c5f 100644
--- a/tests/data/json/filter48.json
+++ b/tests/data/json/filter48.json
@@ -1,13 +1,10 @@
 {
   "op": "<=",
   "args": [
-    {"property":"value"},
+    { "property": "value" },
     {
       "op": "^",
-      "args": [
-        2,
-        {"property":"foo"}
-      ]
+      "args": [2, { "property": "foo" }]
     }
   ]
 }
diff --git a/tests/data/json/filter49.json b/tests/data/json/filter49.json
index ac9beae..3889d73 100644
--- a/tests/data/json/filter49.json
+++ b/tests/data/json/filter49.json
@@ -4,10 +4,7 @@
     0,
     {
       "op": "%",
-      "args": [
-        {"property":"foo"},
-        2
-      ]
+      "args": [{ "property": "foo" }, 2]
     }
   ]
 }
diff --git a/tests/data/json/filter50.json b/tests/data/json/filter50.json
index dca9a5b..799ac20 100644
--- a/tests/data/json/filter50.json
+++ b/tests/data/json/filter50.json
@@ -4,10 +4,7 @@
     1,
     {
       "op": "div",
-      "args": [
-        {"property":"foo"},
-        2
-      ]
+      "args": [{ "property": "foo" }, 2]
     }
   ]
 }
diff --git a/tests/data/json/filter51.json b/tests/data/json/filter51.json
index 5d51ce2..52a88ab 100644
--- a/tests/data/json/filter51.json
+++ b/tests/data/json/filter51.json
@@ -1,13 +1,4 @@
 {
   "op": "a_containedBy",
-  "args": [
-    {
-      "property": "values"
-    },
-    [
-      "a",
-      "b",
-      "c"
-    ]
-  ]
+  "args": [{ "property": "values" }, ["a", "b", "c"]]
 }
diff --git a/tests/data/json/filter52.json b/tests/data/json/filter52.json
index 10f4328..22f2648 100644
--- a/tests/data/json/filter52.json
+++ b/tests/data/json/filter52.json
@@ -1,13 +1,4 @@
 {
   "op": "a_contains",
-  "args": [
-    {
-      "property": "values"
-    },
-    [
-      "a",
-      "b",
-      "c"
-    ]
-  ]
+  "args": [{ "property": "values" }, ["a", "b", "c"]]
 }
diff --git a/tests/data/json/filter53.json b/tests/data/json/filter53.json
index d75e3c8..d86bb8b 100644
--- a/tests/data/json/filter53.json
+++ b/tests/data/json/filter53.json
@@ -1,14 +1,4 @@
 {
   "op": "a_equals",
-  "args": [
-    [
-      "a",
-      true,
-      1.0,
-      8
-    ],
-    {
-      "property": "values"
-    }
-  ]
+  "args": [["a", true, 1.0, 8], { "property": "values" }]
 }
diff --git a/tests/data/json/filter54.json b/tests/data/json/filter54.json
index 1d5cb9b..584e7ae 100644
--- a/tests/data/json/filter54.json
+++ b/tests/data/json/filter54.json
@@ -1,17 +1,7 @@
 {
   "op": "a_overlaps",
   "args": [
-    {
-      "property": "values"
-    },
-    [
-      {
-        "timestamp": "2012-08-10T05:30:00Z"
-      },
-      {
-        "date": "2010-02-10"
-      },
-      false
-    ]
+    { "property": "values" },
+    [{ "timestamp": "2012-08-10T05:30:00Z" }, { "date": "2010-02-10" }, false]
   ]
 }
diff --git a/tests/data/json/filter55.json b/tests/data/json/filter55.json
index beda7c1..796ecd1 100644
--- a/tests/data/json/filter55.json
+++ b/tests/data/json/filter55.json
@@ -4,30 +4,13 @@
     {
       "type": "MultiPoint",
       "coordinates": [
-        [
-          180.0,
-          -0.5
-        ],
-        [
-          179.0,
-          -47.121701
-        ],
-        [
-          180.0,
-          -0.0
-        ],
-        [
-          33.470475,
-          -0.99999
-        ],
-        [
-          179.0,
-          -15.333062
-        ]
+        [180.0, -0.5],
+        [179.0, -47.121701],
+        [180.0, -0.0],
+        [33.470475, -0.99999],
+        [179.0, -15.333062]
       ]
     },
-    {
-      "property": "geometry"
-    }
+    { "property": "geometry" }
   ]
 }
diff --git a/tests/data/json/filter56.json b/tests/data/json/filter56.json
index c4f0cf0..ce33121 100644
--- a/tests/data/json/filter56.json
+++ b/tests/data/json/filter56.json
@@ -6,77 +6,39 @@
       "geometries": [
         {
           "type": "Point",
-          "coordinates": [
-            1.9,
-            2.00001
-          ]
+          "coordinates": [1.9, 2.00001]
         },
         {
           "type": "Point",
-          "coordinates": [
-            0.0,
-            -2.00001
-          ]
+          "coordinates": [0.0, -2.00001]
         },
         {
           "type": "MultiLineString",
           "coordinates": [
             [
-              [
-                -2.00001,
-                -0.0
-              ],
-              [
-                -77.292642,
-                -0.5
-              ],
-              [
-                -87.515626,
-                -0.0
-              ],
-              [
-                -180.0,
-                12.502773
-              ],
-              [
-                21.204842,
-                -1.5
-              ],
-              [
-                -21.878857,
-                -90.0
-              ]
+              [-2.00001, -0.0],
+              [-77.292642, -0.5],
+              [-87.515626, -0.0],
+              [-180.0, 12.502773],
+              [21.204842, -1.5],
+              [-21.878857, -90.0]
             ]
           ]
         },
         {
           "type": "Point",
-          "coordinates": [
-            1.9,
-            0.5
-          ]
+          "coordinates": [1.9, 0.5]
         },
         {
           "type": "LineString",
           "coordinates": [
-            [
-              179.0,
-              1.179148
-            ],
-            [
-              -148.192487,
-              -65.007816
-            ],
-            [
-              0.5,
-              0.333333
-            ]
+            [179.0, 1.179148],
+            [-148.192487, -65.007816],
+            [0.5, 0.333333]
           ]
         }
       ]
     },
-    {
-      "property": "geometry"
-    }
+    { "property": "geometry" }
   ]
 }
diff --git a/tests/data/json/filter57.json b/tests/data/json/filter57.json
index 21c2f43..4eb3c72 100644
--- a/tests/data/json/filter57.json
+++ b/tests/data/json/filter57.json
@@ -1,9 +1,7 @@
 {
   "op": "=",
   "args": [
-    {
-      "property": "value"
-    },
+    { "property": "value" },
     {
       "op": "-",
       "args": [
@@ -15,35 +13,20 @@
               "args": [
                 {
                   "op": "*",
-                  "args": [
-                    -1,
-                    {
-                      "property": "foo"
-                    }
-                  ]
+                  "args": [-1, { "property": "foo" }]
                 },
                 2.0
               ]
             },
             {
               "op": "/",
-              "args": [
-                {
-                  "property": "bar"
-                },
-                6.1234
-              ]
+              "args": [{ "property": "bar" }, 6.1234]
             }
           ]
         },
         {
           "op": "^",
-          "args": [
-            {
-              "property": "x"
-            },
-            2.0
-          ]
+          "args": [{ "property": "x" }, 2.0]
         }
       ]
     }
diff --git a/tests/data/json/filter58.json b/tests/data/json/filter58.json
index e5b244d..6915bd8 100644
--- a/tests/data/json/filter58.json
+++ b/tests/data/json/filter58.json
@@ -1,11 +1,4 @@
 {
   "op": "like",
-  "args": [
-    {
-      "property": "name"
-    },
-    {
-      "casei": "FOO%"
-    }
-  ]
+  "args": [{ "property": "name" }, { "op": "casei", "args": ["FOO%"] }]
 }
diff --git a/tests/data/json/filter59.json b/tests/data/json/filter59.json
index 7b2a1ab..aebd58f 100644
--- a/tests/data/json/filter59.json
+++ b/tests/data/json/filter59.json
@@ -1,13 +1,10 @@
 {
   "op": "=",
   "args": [
+    { "property": "owner" },
     {
-      "property": "owner"
-    },
-    {
-      "casei": {
-        "accenti": "Beyoncé"
-      }
+      "op": "casei",
+      "args": [{ "op": "accenti", "args": ["Beyoncé"] }]
     }
   ]
 }
diff --git a/tests/data/json/filter60.json b/tests/data/json/filter60.json
new file mode 100644
index 0000000..6d19de3
--- /dev/null
+++ b/tests/data/json/filter60.json
@@ -0,0 +1,4 @@
+{
+  "op": "=",
+  "args": [{ "property": "name" }, "King's Landing"]
+}
diff --git a/tests/data/json/filter61.json b/tests/data/json/filter61.json
new file mode 100644
index 0000000..033d644
--- /dev/null
+++ b/tests/data/json/filter61.json
@@ -0,0 +1,10 @@
+{
+  "op": "or",
+  "args": [
+    {
+      "op": "not",
+      "args": [{ "op": ">", "args": [{ "property": "value" }, 10] }]
+    },
+    { "op": "=", "args": [{ "property": "foo" }, 2] }
+  ]
+}
diff --git a/tests/data/text/filter55-alt01.txt b/tests/data/text/filter55-alt01.txt
new file mode 100644
index 0000000..4aaa886
--- /dev/null
+++ b/tests/data/text/filter55-alt01.txt
@@ -0,0 +1,1 @@
+S_EQUALS(MULTIPOINT (180.0 -0.5, 179.0 -47.121701, 180.0 -0.0, 33.470475 -0.99999, 179.0 -15.333062), "geometry")
diff --git a/tests/data/text/filter60-alt01.txt b/tests/data/text/filter60-alt01.txt
new file mode 100644
index 0000000..95487a3
--- /dev/null
+++ b/tests/data/text/filter60-alt01.txt
@@ -0,0 +1,1 @@
+"name" = 'King''s Landing'
diff --git a/tests/data/text/filter60.txt b/tests/data/text/filter60.txt
new file mode 100644
index 0000000..fd24d61
--- /dev/null
+++ b/tests/data/text/filter60.txt
@@ -0,0 +1,1 @@
+"name" = 'King\'s Landing'
diff --git a/tests/data/text/filter61-alt01.txt b/tests/data/text/filter61-alt01.txt
new file mode 100644
index 0000000..651bb65
--- /dev/null
+++ b/tests/data/text/filter61-alt01.txt
@@ -0,0 +1,1 @@
+NOT (value > 10) OR (foo = 2)
diff --git a/tests/data/text/filter61.txt b/tests/data/text/filter61.txt
new file mode 100644
index 0000000..7c76099
--- /dev/null
+++ b/tests/data/text/filter61.txt
@@ -0,0 +1,1 @@
+(NOT "value" > 10 OR "foo" = 2)
diff --git a/tests/test_cql2_pydantic.py b/tests/test_cql2_pydantic.py
deleted file mode 100644
index 8713f32..0000000
--- a/tests/test_cql2_pydantic.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from pycql2.cql2_pydantic import join_list, make_char_literal
-
-
-def test_make_char_literal() -> None:
-    assert make_char_literal("a") == "'a'"
-    assert make_char_literal("a'b") == "'a''b'"
-    assert make_char_literal("a''b") == "'a''''b'"
-
-
-def test_join_list() -> None:
-    assert join_list([True, False], ",") == "True,False"
-    assert join_list([1, 2, 3], " ") == "1 2 3"
-    assert join_list([1.0, 2.0, 3.0], ", ") == "1.0, 2.0, 3.0"
-    assert join_list(["a", "b", "c"], "") == "abc"
diff --git a/tests/test_pycql2.py b/tests/test_pycql2.py
index 385f70f..0c100b6 100644
--- a/tests/test_pycql2.py
+++ b/tests/test_pycql2.py
@@ -1,5 +1,4 @@
-import json
-import os
+from pathlib import Path
 
 import pytest
 from lark import UnexpectedCharacters
@@ -8,87 +7,77 @@
 from pycql2.cql2_pydantic import BooleanExpression
 from pycql2.cql2_transformer import parser, transformer
 
-JSON_DIR = "tests/data/json"
-TEXT_DIR = "tests/data/text"
-json_files = os.listdir(JSON_DIR)
-text_files = os.listdir(TEXT_DIR)
+JSON_DIR = Path("tests/data/json")
+TEXT_DIR = Path("tests/data/text")
+json_files = sorted(_.name for _ in JSON_DIR.glob("*.json"))
+text_files = sorted(_.name for _ in TEXT_DIR.glob("*.txt"))
 
-BAD_JSON_DIR = "tests/bad_data/json"
-BAD_TEXT_DIR = "tests/bad_data/text"
-bad_json_files = os.listdir(BAD_JSON_DIR)
-bad_text_files = os.listdir(BAD_TEXT_DIR)
+BAD_JSON_DIR = Path("tests/bad_data/json")
+BAD_TEXT_DIR = Path("tests/bad_data/text")
+bad_json_files = sorted(_.name for _ in BAD_JSON_DIR.glob("*.json"))
+bad_text_files = sorted(_.name for _ in BAD_TEXT_DIR.glob("*.txt"))
 
 
-@pytest.mark.parametrize("json_file", json_files)
-def test_parse_json(json_file: str) -> None:
-    # Simply test that the pydantic model can parse the json
-    with open(os.path.join(JSON_DIR, json_file)) as inf:
-        json_data = json.load(inf)
-    _ = BooleanExpression.model_validate(json_data)
+@pytest.mark.parametrize("file_name", json_files)
+def test_parse_json(file_name: str) -> None:
+    """Test that the pydantic model can parse the json."""
 
+    json_file = JSON_DIR / file_name
+    _ = BooleanExpression.model_validate_json(json_file.read_text())
 
-@pytest.mark.parametrize("text_file", text_files)
-def test_parse_text(text_file: str) -> None:
-    # Simply test that the parser can parse the text
-    with open(os.path.join(TEXT_DIR, text_file)) as inf:
-        text_data = inf.read()
-    _ = parser.parse(text_data)
 
+@pytest.mark.parametrize("file_name", text_files)
+def test_parse_text(file_name: str) -> None:
+    """Test that the parser can parse the text."""
 
-@pytest.mark.parametrize("json_file", bad_json_files)
-def test_parse_bad_json(json_file: str) -> None:
-    # Simply test that the pydantic model can parse the json
-    with open(os.path.join(BAD_JSON_DIR, json_file)) as inf:
-        json_data = json.load(inf)
+    text_file = TEXT_DIR / file_name
+    _ = parser.parse(text_file.read_text())
+
+
+@pytest.mark.parametrize("file_name", bad_json_files)
+def test_parse_bad_json(file_name: str) -> None:
+    """Test that the pydantic model does not parse the bad json."""
+
+    json_file = BAD_JSON_DIR / file_name
     with pytest.raises(ValidationError):
-        _ = BooleanExpression.model_validate(json_data)
+        _ = BooleanExpression.model_validate_json(json_file.read_text())
 
 
-@pytest.mark.parametrize("text_file", bad_text_files)
-def test_parse_bad_text(text_file: str) -> None:
-    # Simply test that the parser can parse the text
-    with open(os.path.join(BAD_TEXT_DIR, text_file)) as inf:
-        text_data = inf.read()
+@pytest.mark.parametrize("file_name", bad_text_files)
+def test_parse_bad_text(file_name: str) -> None:
+    """Test that the parser does not parse the bad text."""
+
+    text_file = BAD_TEXT_DIR / file_name
     with pytest.raises(UnexpectedCharacters):
-        _ = parser.parse(text_data)
+        _ = parser.parse(text_file.read_text())
 
 
-@pytest.mark.parametrize("json_file", json_files)
-def test_json_to_text(json_file: str) -> None:
-    # Test that the pydantic model outputs the correct text representation
+@pytest.mark.parametrize("file_name", json_files)
+def test_json_to_text(file_name: str) -> None:
+    """Test that the pydantic model outputs the correct text representation."""
 
+    json_file = JSON_DIR / file_name
     # Load the json data
-    with open(os.path.join(JSON_DIR, json_file)) as inf:
-        json_data = json.load(inf)
-    model = BooleanExpression.model_validate(json_data)
+    model = BooleanExpression.model_validate_json(json_file.read_text())
     # Load the expected text data
-    text_file = json_file.replace(".json", ".txt")
-    with open(os.path.join(TEXT_DIR, text_file)) as inf:
-        expected = inf.read().strip()
+    text_file = TEXT_DIR / (json_file.stem + ".txt")
+    expected = text_file.read_text().strip()
     # Compare the two
     assert str(model) == expected
 
 
-@pytest.mark.parametrize("text_file", text_files)
-def test_text_to_json(text_file: str) -> None:
-    # Test that the parser outputs the correct json representation
+@pytest.mark.parametrize("file_name", text_files)
+def test_text_to_json(file_name: str) -> None:
+    """Test that the parser outputs the correct json representation."""
 
+    text_file = TEXT_DIR / file_name
     # Load the text data
-    with open(os.path.join(TEXT_DIR, text_file)) as inf:
-        text_data = inf.read()
-    model = parser.parse(text_data)
-
+    tree = parser.parse(text_file.read_text())
+    output = transformer.transform(tree)
+    # Some text files are equivalent to the same json file. These contain
+    # "-alt" in the file names. We split on the "-" to get the base name.
+    json_file = JSON_DIR / (text_file.stem.split("-")[0] + ".json")
     # Load the expected json data
-    json_file = text_file.replace(".txt", ".json")
-    # Some text files have an "alt" suffix, which means that they are
-    # equivalent to the same json file.  In this case, we want to load
-    # the json file without the "alt" suffix.
-    if "alt" in json_file:
-        json_file = json_file.split("-")[0] + ".json"
-    with open(os.path.join(JSON_DIR, json_file)) as inf:
-        json_data = json.load(inf)
-    expected = BooleanExpression.model_validate(json_data)
-    # Compare the two. We use __root__ because the json data is wrapped
-    # by the BooleanExpression model.
-    output = transformer.transform(model)
+    expected = BooleanExpression.model_validate_json(json_file.read_text())
+    # Compare the two
     assert output == expected
diff --git a/tests/test_pycql2_hypothesis.py b/tests/test_pycql2_hypothesis.py
index a50fe70..ca30c4a 100644
--- a/tests/test_pycql2_hypothesis.py
+++ b/tests/test_pycql2_hypothesis.py
@@ -5,18 +5,24 @@
 from lark import Lark, Token
 from lark.grammar import Terminal
 
-from pycql2.cql2_pydantic import join_list
+from pycql2.cql2_pydantic import _join_list
 from pycql2.cql2_transformer import parser, transformer
 
 
 class SpacedLarkStrategy(LarkStrategy):
     def __init__(
-        self, grammar: Lark, start: str, explicit: Optional[Dict] = None
+        self,
+        grammar: Lark,
+        start: Optional[str] = None,
+        explicit: Optional[Dict[str, st.SearchStrategy[str]]] = None,
+        alphabet: Optional[st.SearchStrategy[str]] = None,
     ) -> None:
         if explicit is None:
             explicit = {}
+        if alphabet is None:
+            alphabet = st.characters(codec="utf-8")
         # Let LarkStrategy figure out most of the strategy
-        super().__init__(grammar, start, explicit)
+        super().__init__(grammar, start, explicit, alphabet)
 
         # The logic used to draw symbols requires the actual strategies be in "Terminals"
         # so we can create new Terminal strategies that doesn't clash with the existing ones
@@ -35,7 +41,7 @@ def __init__(
                     min_size=3,
                 )
                 .map(lambda x: [*x, x[0]])
-                .map(lambda x: join_list([join_list(c, " ") for c in x], ","))
+                .map(lambda x: _join_list([_join_list(c, " ") for c in x], ","))
                 .map(lambda x: f"({x})")
             ),
             # The grammar just uses DIGIT throughout but we need to make sure that the
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 0000000..aa9926a
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,51 @@
+from typing import List, Sequence, Tuple
+
+import pytest
+
+from pycql2.cql2_pydantic import _join_list, _make_char_literal
+from pycql2.cql2_transformer import _clean_char_literal
+
+# Cases for testing round trip of 'Character Literals' with
+# various numbers of single quotes in the strings.
+
+# First value is the Python string
+# Second value is CQL2 Character Literal
+CHAR_LITERALS: List[Tuple[str, str]] = [
+    # None
+    ("a", r"'a'"),
+    # One
+    ("a'b", r"'a\'b'"),
+    # Multiple
+    ("a'b'c", r"'a\'b\'c'"),
+    # Repeated
+    ("a''b", r"'a\'\'b'"),
+    # Starts with
+    ("'a", r"'\'a'"),
+    # Ends with
+    ("a'", r"'a\''"),
+    # Starts with, repeated, ends with
+    ("'a''b'", r"'\'a\'\'b\''"),
+]
+
+
+@pytest.mark.parametrize(("input", "expected"), CHAR_LITERALS)
+def test_make_char_literal(input: str, expected: str) -> None:
+    assert _make_char_literal(input) == expected
+
+
+@pytest.mark.parametrize(("expected", "input"), CHAR_LITERALS)
+def test_clean_char_literal(input: str, expected: str) -> None:
+    assert _clean_char_literal(input) == expected
+
+
+@pytest.mark.parametrize(
+    ("items", "sep", "expected"),
+    [
+        ((True, False), ",", "True,False"),
+        ((1, 2, 3), " ", "1 2 3"),
+        ((1.0, 2.0, 3.0), ", ", "1.0, 2.0, 3.0"),
+        (("a", "b", "c"), "", "abc"),
+    ],
+)
+def test_join_list(items: Sequence, sep: str, expected: str) -> None:
+    assert _join_list(items, sep) == expected