Repository: siapy/siapy-lib (language: python)

Issue:
Add missing __all__ attributes 
## Description

<!--
    - Add 1-2 sentences explaining the purpose of this PR.
    - (Optionally] Add commit ID and [GitHub will autolink](https://help.github.com/en/github/writing-on-github/autolinked-references-and-urls).
    - (Optionally] Reference related commits, issues and pull requests. Type `#` and select from the list.
-->

## Checklist

- [ ] I have reviewed the [Guidelines for Contributing](../CONTRIBUTING.md) and the [Code of Conduct](../CODE_OF_CONDUCT.md).



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml
index 5cdccae..11f1c74 100644
--- a/.github/workflows/issue-manager.yml
+++ b/.github/workflows/issue-manager.yml
@@ -16,7 +16,7 @@ jobs:
   issue-manager:
     runs-on: ubuntu-latest
     steps:
-      - uses: tiangolo/issue-manager@0.5.0
+      - uses: tiangolo/issue-manager@0.5.1
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
           config: >
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a78ee85..888954f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,14 +14,14 @@ repos:
       - id: end-of-file-fixer
       - id: trailing-whitespace
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.6.2
+    rev: v0.6.5
     hooks:
       - id: ruff
         args:
           - --fix
       - id: ruff-format
   - repo: https://github.com/gitleaks/gitleaks
-    rev: v8.18.4
+    rev: v8.19.2
     hooks:
       - id: gitleaks
   - repo: https://github.com/codespell-project/codespell
diff --git a/pdm.lock b/pdm.lock
index 02107c9..d1ce604 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -444,7 +444,7 @@ files = [
 
 [[package]]
 name = "dask"
-version = "2024.8.1"
+version = "2024.8.2"
 requires_python = ">=3.10"
 summary = "Parallel PyData with Task Scheduling"
 groups = ["default"]
@@ -459,58 +459,58 @@ dependencies = [
     "toolz>=0.10.0",
 ]
 files = [
-    {file = "dask-2024.8.1-py3-none-any.whl", hash = "sha256:b8b58cba91dc9c057c8676dcc80b8bc321602b4dfd21529d33b03b55d428e2c3"},
-    {file = "dask-2024.8.1.tar.gz", hash = "sha256:4254e43ac8c3affad2b22952f126b00a00f52c87caae91c068d8e395a4ad1a72"},
+    {file = "dask-2024.8.2-py3-none-any.whl", hash = "sha256:484c317ee870140dbeccc759a91fd98821bba98d71a43eda7856fc9e859b03a1"},
+    {file = "dask-2024.8.2.tar.gz", hash = "sha256:1d05e09166ac7927a54401e998aa1599e86aac666edbdaeb3096bf4b957029e0"},
 ]
 
 [[package]]
 name = "dask-expr"
-version = "1.1.11"
+version = "1.1.13"
 requires_python = ">=3.10"
 summary = "High Level Expressions for Dask "
 groups = ["default"]
 dependencies = [
-    "dask==2024.8.1",
+    "dask==2024.8.2",
     "pandas>=2",
-    "pyarrow>=7.0.0",
+    "pyarrow>=14.0.1",
 ]
 files = [
-    {file = "dask_expr-1.1.11-py3-none-any.whl", hash = "sha256:b9222b3d430152e3af4a1777f66bcee88651f510876cb57c720107d123d9ba63"},
-    {file = "dask_expr-1.1.11.tar.gz", hash = "sha256:275689c269f9c30dbaf9d8d7e9d3b5ac5438ea6db73fdbf95b3f4bfb1381bc5a"},
+    {file = "dask_expr-1.1.13-py3-none-any.whl", hash = "sha256:0a68a8d18cae72b172ee3afc3f33253f065becbf8fbf9f3ee43e665ec5499112"},
+    {file = "dask_expr-1.1.13.tar.gz", hash = "sha256:7e00b2c6538e6c633e22db262abfe3aeb6dc65c1de4a8eadcb2bbf44ad8729da"},
 ]
 
 [[package]]
 name = "dask"
-version = "2024.8.1"
+version = "2024.8.2"
 extras = ["array"]
 requires_python = ">=3.10"
 summary = "Parallel PyData with Task Scheduling"
 groups = ["default"]
 dependencies = [
-    "dask==2024.8.1",
-    "numpy>=1.21",
+    "dask==2024.8.2",
+    "numpy>=1.24",
 ]
 files = [
-    {file = "dask-2024.8.1-py3-none-any.whl", hash = "sha256:b8b58cba91dc9c057c8676dcc80b8bc321602b4dfd21529d33b03b55d428e2c3"},
-    {file = "dask-2024.8.1.tar.gz", hash = "sha256:4254e43ac8c3affad2b22952f126b00a00f52c87caae91c068d8e395a4ad1a72"},
+    {file = "dask-2024.8.2-py3-none-any.whl", hash = "sha256:484c317ee870140dbeccc759a91fd98821bba98d71a43eda7856fc9e859b03a1"},
+    {file = "dask-2024.8.2.tar.gz", hash = "sha256:1d05e09166ac7927a54401e998aa1599e86aac666edbdaeb3096bf4b957029e0"},
 ]
 
 [[package]]
 name = "dask"
-version = "2024.8.1"
+version = "2024.8.2"
 extras = ["dataframe"]
 requires_python = ">=3.10"
 summary = "Parallel PyData with Task Scheduling"
 groups = ["default"]
 dependencies = [
     "dask-expr<1.2,>=1.1",
-    "dask==2024.8.1",
+    "dask==2024.8.2",
     "dask[array]",
     "pandas>=2.0",
 ]
 files = [
-    {file = "dask-2024.8.1-py3-none-any.whl", hash = "sha256:b8b58cba91dc9c057c8676dcc80b8bc321602b4dfd21529d33b03b55d428e2c3"},
-    {file = "dask-2024.8.1.tar.gz", hash = "sha256:4254e43ac8c3affad2b22952f126b00a00f52c87caae91c068d8e395a4ad1a72"},
+    {file = "dask-2024.8.2-py3-none-any.whl", hash = "sha256:484c317ee870140dbeccc759a91fd98821bba98d71a43eda7856fc9e859b03a1"},
+    {file = "dask-2024.8.2.tar.gz", hash = "sha256:1d05e09166ac7927a54401e998aa1599e86aac666edbdaeb3096bf4b957029e0"},
 ]
 
 [[package]]
@@ -949,7 +949,7 @@ files = [
 
 [[package]]
 name = "griffe-typingdoc"
-version = "0.2.6"
+version = "0.2.7"
 requires_python = ">=3.8"
 summary = "Griffe extension for PEP 727 – Documentation Metadata in Typing."
 groups = ["docs"]
@@ -958,8 +958,8 @@ dependencies = [
     "typing-extensions>=4.7",
 ]
 files = [
-    {file = "griffe_typingdoc-0.2.6-py3-none-any.whl", hash = "sha256:2726e6cf1e986f42fe9cab4a95cef103a745327f035a32055816849ca47893e4"},
-    {file = "griffe_typingdoc-0.2.6.tar.gz", hash = "sha256:852a17c1e2d29bbbf14a287e7cc5982669343cf60a4ea1618e486eb57aba3248"},
+    {file = "griffe_typingdoc-0.2.7-py3-none-any.whl", hash = "sha256:74a825df32fc87fcae2f221df5c5524dca23155cd3c04ec9fa46493669d3cf54"},
+    {file = "griffe_typingdoc-0.2.7.tar.gz", hash = "sha256:800841e99f8844ea3c1fae80b19bede7d8eed4195a2586f5db753f7a73f4931d"},
 ]
 
 [[package]]
@@ -1391,7 +1391,7 @@ files = [
 
 [[package]]
 name = "mkdocs"
-version = "1.6.0"
+version = "1.6.1"
 requires_python = ">=3.8"
 summary = "Project documentation with Markdown."
 groups = ["docs"]
@@ -1412,13 +1412,13 @@ dependencies = [
     "watchdog>=2.0",
 ]
 files = [
-    {file = "mkdocs-1.6.0-py3-none-any.whl", hash = "sha256:1eb5cb7676b7d89323e62b56235010216319217d4af5ddc543a91beb8d125ea7"},
-    {file = "mkdocs-1.6.0.tar.gz", hash = "sha256:a73f735824ef83a4f3bcb7a231dcab23f5a838f88b7efc54a0eef5fbdbc3c512"},
+    {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"},
+    {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"},
 ]
 
 [[package]]
 name = "mkdocs-autorefs"
-version = "1.0.1"
+version = "1.2.0"
 requires_python = ">=3.8"
 summary = "Automatically link across pages in MkDocs."
 groups = ["docs"]
@@ -1428,8 +1428,8 @@ dependencies = [
     "mkdocs>=1.1",
 ]
 files = [
-    {file = "mkdocs_autorefs-1.0.1-py3-none-any.whl", hash = "sha256:aacdfae1ab197780fb7a2dac92ad8a3d8f7ca8049a9cbe56a4218cd52e8da570"},
-    {file = "mkdocs_autorefs-1.0.1.tar.gz", hash = "sha256:f684edf847eced40b570b57846b15f0bf57fb93ac2c510450775dcf16accb971"},
+    {file = "mkdocs_autorefs-1.2.0-py3-none-any.whl", hash = "sha256:d588754ae89bd0ced0c70c06f58566a4ee43471eeeee5202427da7de9ef85a2f"},
+    {file = "mkdocs_autorefs-1.2.0.tar.gz", hash = "sha256:a86b93abff653521bda71cf3fc5596342b7a23982093915cb74273f67522190f"},
 ]
 
 [[package]]
@@ -1478,7 +1478,7 @@ files = [
 
 [[package]]
 name = "mkdocs-material"
-version = "9.5.32"
+version = "9.5.34"
 requires_python = ">=3.8"
 summary = "Documentation that simply works"
 groups = ["docs"]
@@ -1496,8 +1496,8 @@ dependencies = [
     "requests~=2.26",
 ]
 files = [
-    {file = "mkdocs_material-9.5.32-py3-none-any.whl", hash = "sha256:f3704f46b63d31b3cd35c0055a72280bed825786eccaf19c655b44e0cd2c6b3f"},
-    {file = "mkdocs_material-9.5.32.tar.gz", hash = "sha256:38ed66e6d6768dde4edde022554553e48b2db0d26d1320b19e2e2b9da0be1120"},
+    {file = "mkdocs_material-9.5.34-py3-none-any.whl", hash = "sha256:54caa8be708de2b75167fd4d3b9f3d949579294f49cb242515d4653dbee9227e"},
+    {file = "mkdocs_material-9.5.34.tar.gz", hash = "sha256:1e60ddf716cfb5679dfd65900b8a25d277064ed82d9a53cd5190e3f894df7840"},
 ]
 
 [[package]]
@@ -1540,40 +1540,41 @@ files = [
 
 [[package]]
 name = "mkdocstrings"
-version = "0.25.2"
+version = "0.26.1"
 requires_python = ">=3.8"
 summary = "Automatic documentation from sources, for MkDocs."
 groups = ["docs"]
 dependencies = [
     "Jinja2>=2.11.1",
-    "Markdown>=3.3",
+    "Markdown>=3.6",
     "MarkupSafe>=1.1",
     "click>=7.0",
     "importlib-metadata>=4.6; python_version < \"3.10\"",
-    "mkdocs-autorefs>=0.3.1",
+    "mkdocs-autorefs>=1.2",
     "mkdocs>=1.4",
-    "platformdirs>=2.2.0",
+    "platformdirs>=2.2",
     "pymdown-extensions>=6.3",
     "typing-extensions>=4.1; python_version < \"3.10\"",
 ]
 files = [
-    {file = "mkdocstrings-0.25.2-py3-none-any.whl", hash = "sha256:9e2cda5e2e12db8bb98d21e3410f3f27f8faab685a24b03b06ba7daa5b92abfc"},
-    {file = "mkdocstrings-0.25.2.tar.gz", hash = "sha256:5cf57ad7f61e8be3111a2458b4e49c2029c9cb35525393b179f9c916ca8042dc"},
+    {file = "mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf"},
+    {file = "mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33"},
 ]
 
 [[package]]
 name = "mkdocstrings-python"
-version = "1.10.8"
+version = "1.11.1"
 requires_python = ">=3.8"
 summary = "A Python handler for mkdocstrings."
 groups = ["docs"]
 dependencies = [
     "griffe>=0.49",
-    "mkdocstrings>=0.25",
+    "mkdocs-autorefs>=1.2",
+    "mkdocstrings>=0.26",
 ]
 files = [
-    {file = "mkdocstrings_python-1.10.8-py3-none-any.whl", hash = "sha256:bb12e76c8b071686617f824029cb1dfe0e9afe89f27fb3ad9a27f95f054dcd89"},
-    {file = "mkdocstrings_python-1.10.8.tar.gz", hash = "sha256:5856a59cbebbb8deb133224a540de1ff60bded25e54d8beacc375bb133d39016"},
+    {file = "mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af"},
+    {file = "mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322"},
 ]
 
 [[package]]
@@ -1606,7 +1607,7 @@ files = [
 
 [[package]]
 name = "mypy"
-version = "1.11.1"
+version = "1.11.2"
 requires_python = ">=3.8"
 summary = "Optional static typing for Python"
 groups = ["lint"]
@@ -1616,23 +1617,23 @@ dependencies = [
     "typing-extensions>=4.6.0",
 ]
 files = [
-    {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"},
-    {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"},
-    {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"},
-    {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"},
-    {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"},
-    {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"},
-    {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"},
-    {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"},
-    {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"},
-    {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"},
-    {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"},
-    {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"},
-    {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"},
-    {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"},
-    {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"},
-    {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"},
-    {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"},
+    {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
+    {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
+    {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"},
+    {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"},
+    {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"},
+    {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"},
+    {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"},
+    {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"},
+    {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"},
+    {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"},
+    {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"},
+    {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"},
+    {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"},
+    {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"},
+    {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"},
+    {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"},
+    {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"},
 ]
 
 [[package]]
@@ -1764,7 +1765,7 @@ files = [
 
 [[package]]
 name = "optuna"
-version = "3.6.1"
+version = "4.0.0"
 requires_python = ">=3.7"
 summary = "A hyperparameter optimization framework"
 groups = ["default"]
@@ -1778,8 +1779,8 @@ dependencies = [
     "tqdm",
 ]
 files = [
-    {file = "optuna-3.6.1-py3-none-any.whl", hash = "sha256:b32e0490bd6552790b70ec94de77dd2855057c9e229cd9f4da48fe8a31c7f1cc"},
-    {file = "optuna-3.6.1.tar.gz", hash = "sha256:146e530b57b4b9afd7526b3e642fbe65491f7e292b405913355f8e438e361ecf"},
+    {file = "optuna-4.0.0-py3-none-any.whl", hash = "sha256:a825c32d13f6085bcb2229b2724a5078f2e0f61a7533e800e580ce41a8c6c10d"},
+    {file = "optuna-4.0.0.tar.gz", hash = "sha256:844949f09e2a7353ab414e9cfd783cf0a647a65fc32a7236212ed6a37fe08973"},
 ]
 
 [[package]]
@@ -1843,8 +1844,8 @@ files = [
 
 [[package]]
 name = "pandas-stubs"
-version = "2.2.2.240807"
-requires_python = ">=3.9"
+version = "2.2.2.240909"
+requires_python = ">=3.10"
 summary = "Type annotations for pandas"
 groups = ["lint"]
 dependencies = [
@@ -1852,8 +1853,8 @@ dependencies = [
     "types-pytz>=2022.1.1",
 ]
 files = [
-    {file = "pandas_stubs-2.2.2.240807-py3-none-any.whl", hash = "sha256:893919ad82be4275f0d07bb47a95d08bae580d3fdea308a7acfcb3f02e76186e"},
-    {file = "pandas_stubs-2.2.2.240807.tar.gz", hash = "sha256:64a559725a57a449f46225fbafc422520b7410bff9252b661a225b5559192a93"},
+    {file = "pandas_stubs-2.2.2.240909-py3-none-any.whl", hash = "sha256:e230f5fa4065f9417804f4d65cd98f86c002efcc07933e8abcd48c3fad9c30a2"},
+    {file = "pandas_stubs-2.2.2.240909.tar.gz", hash = "sha256:3c0951a2c3e45e3475aed9d80b7147ae82f176b9e42e9fb321cfdebf3d411b3d"},
 ]
 
 [[package]]
@@ -2098,24 +2099,24 @@ files = [
 
 [[package]]
 name = "pydantic"
-version = "2.8.2"
+version = "2.9.1"
 requires_python = ">=3.8"
 summary = "Data validation using Python type hints"
 groups = ["default"]
 dependencies = [
-    "annotated-types>=0.4.0",
-    "pydantic-core==2.20.1",
+    "annotated-types>=0.6.0",
+    "pydantic-core==2.23.3",
     "typing-extensions>=4.12.2; python_version >= \"3.13\"",
     "typing-extensions>=4.6.1; python_version < \"3.13\"",
 ]
 files = [
-    {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
-    {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
+    {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"},
+    {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"},
 ]
 
 [[package]]
 name = "pydantic-core"
-version = "2.20.1"
+version = "2.23.3"
 requires_python = ">=3.8"
 summary = "Core functionality for Pydantic validation and serialization"
 groups = ["default"]
@@ -2123,63 +2124,63 @@ dependencies = [
     "typing-extensions!=4.7.0,>=4.6.0",
 ]
 files = [
-    {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
-    {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
-    {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
-    {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
-    {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
-    {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
-    {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
-    {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
-    {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
-    {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
-    {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
-    {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
-    {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
-    {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
-    {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"},
+    {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"},
+    {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"},
+    {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"},
+    {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"},
+    {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"},
+    {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"},
+    {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"},
+    {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"},
+    {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"},
+    {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"},
+    {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"},
+    {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"},
+    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"},
+    {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"},
 ]
 
 [[package]]
@@ -2232,7 +2233,7 @@ files = [
 
 [[package]]
 name = "pytest"
-version = "8.3.2"
+version = "8.3.3"
 requires_python = ">=3.8"
 summary = "pytest: simple powerful testing with Python"
 groups = ["test"]
@@ -2245,8 +2246,8 @@ dependencies = [
     "tomli>=1; python_version < \"3.11\"",
 ]
 files = [
-    {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
-    {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
+    {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
+    {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
 ]
 
 [[package]]
@@ -2445,7 +2446,7 @@ files = [
 
 [[package]]
 name = "rich"
-version = "13.7.1"
+version = "13.8.1"
 requires_python = ">=3.7.0"
 summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 groups = ["default"]
@@ -2455,8 +2456,8 @@ dependencies = [
     "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"",
 ]
 files = [
-    {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
-    {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
+    {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"},
+    {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"},
 ]
 
 [[package]]
@@ -2475,29 +2476,29 @@ files = [
 
 [[package]]
 name = "ruff"
-version = "0.6.1"
+version = "0.6.4"
 requires_python = ">=3.7"
 summary = "An extremely fast Python linter and code formatter, written in Rust."
 groups = ["lint"]
 files = [
-    {file = "ruff-0.6.1-py3-none-linux_armv6l.whl", hash = "sha256:b4bb7de6a24169dc023f992718a9417380301b0c2da0fe85919f47264fb8add9"},
-    {file = "ruff-0.6.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45efaae53b360c81043e311cdec8a7696420b3d3e8935202c2846e7a97d4edae"},
-    {file = "ruff-0.6.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bc60c7d71b732c8fa73cf995efc0c836a2fd8b9810e115be8babb24ae87e0850"},
-    {file = "ruff-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c7477c3b9da822e2db0b4e0b59e61b8a23e87886e727b327e7dcaf06213c5cf"},
-    {file = "ruff-0.6.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0af7ab3f86e3dc9f157a928e08e26c4b40707d0612b01cd577cc84b8905cc9"},
-    {file = "ruff-0.6.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392688dbb50fecf1bf7126731c90c11a9df1c3a4cdc3f481b53e851da5634fa5"},
-    {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5278d3e095ccc8c30430bcc9bc550f778790acc211865520f3041910a28d0024"},
-    {file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe6d5f65d6f276ee7a0fc50a0cecaccb362d30ef98a110f99cac1c7872df2f18"},
-    {file = "ruff-0.6.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e0dd11e2ae553ee5c92a81731d88a9883af8db7408db47fc81887c1f8b672e"},
-    {file = "ruff-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d812615525a34ecfc07fd93f906ef5b93656be01dfae9a819e31caa6cfe758a1"},
-    {file = "ruff-0.6.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faaa4060f4064c3b7aaaa27328080c932fa142786f8142aff095b42b6a2eb631"},
-    {file = "ruff-0.6.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99d7ae0df47c62729d58765c593ea54c2546d5de213f2af2a19442d50a10cec9"},
-    {file = "ruff-0.6.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9eb18dfd7b613eec000e3738b3f0e4398bf0153cb80bfa3e351b3c1c2f6d7b15"},
-    {file = "ruff-0.6.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c62bc04c6723a81e25e71715aa59489f15034d69bf641df88cb38bdc32fd1dbb"},
-    {file = "ruff-0.6.1-py3-none-win32.whl", hash = "sha256:9fb4c4e8b83f19c9477a8745e56d2eeef07a7ff50b68a6998f7d9e2e3887bdc4"},
-    {file = "ruff-0.6.1-py3-none-win_amd64.whl", hash = "sha256:c2ebfc8f51ef4aca05dad4552bbcf6fe8d1f75b2f6af546cc47cc1c1ca916b5b"},
-    {file = "ruff-0.6.1-py3-none-win_arm64.whl", hash = "sha256:3bc81074971b0ffad1bd0c52284b22411f02a11a012082a76ac6da153536e014"},
-    {file = "ruff-0.6.1.tar.gz", hash = "sha256:af3ffd8c6563acb8848d33cd19a69b9bfe943667f0419ca083f8ebe4224a3436"},
+    {file = "ruff-0.6.4-py3-none-linux_armv6l.whl", hash = "sha256:c4b153fc152af51855458e79e835fb6b933032921756cec9af7d0ba2aa01a258"},
+    {file = "ruff-0.6.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bedff9e4f004dad5f7f76a9d39c4ca98af526c9b1695068198b3bda8c085ef60"},
+    {file = "ruff-0.6.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d02a4127a86de23002e694d7ff19f905c51e338c72d8e09b56bfb60e1681724f"},
+    {file = "ruff-0.6.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7862f42fc1a4aca1ea3ffe8a11f67819d183a5693b228f0bb3a531f5e40336fc"},
+    {file = "ruff-0.6.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebe4ff1967c838a1a9618a5a59a3b0a00406f8d7eefee97c70411fefc353617"},
+    {file = "ruff-0.6.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:932063a03bac394866683e15710c25b8690ccdca1cf192b9a98260332ca93408"},
+    {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:50e30b437cebef547bd5c3edf9ce81343e5dd7c737cb36ccb4fe83573f3d392e"},
+    {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44536df7b93a587de690e124b89bd47306fddd59398a0fb12afd6133c7b3818"},
+    {file = "ruff-0.6.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ea086601b22dc5e7693a78f3fcfc460cceabfdf3bdc36dc898792aba48fbad6"},
+    {file = "ruff-0.6.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b52387d3289ccd227b62102c24714ed75fbba0b16ecc69a923a37e3b5e0aaaa"},
+    {file = "ruff-0.6.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0308610470fcc82969082fc83c76c0d362f562e2f0cdab0586516f03a4e06ec6"},
+    {file = "ruff-0.6.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:803b96dea21795a6c9d5bfa9e96127cc9c31a1987802ca68f35e5c95aed3fc0d"},
+    {file = "ruff-0.6.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:66dbfea86b663baab8fcae56c59f190caba9398df1488164e2df53e216248baa"},
+    {file = "ruff-0.6.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:34d5efad480193c046c86608dbba2bccdc1c5fd11950fb271f8086e0c763a5d1"},
+    {file = "ruff-0.6.4-py3-none-win32.whl", hash = "sha256:f0f8968feea5ce3777c0d8365653d5e91c40c31a81d95824ba61d871a11b8523"},
+    {file = "ruff-0.6.4-py3-none-win_amd64.whl", hash = "sha256:549daccee5227282289390b0222d0fbee0275d1db6d514550d65420053021a58"},
+    {file = "ruff-0.6.4-py3-none-win_arm64.whl", hash = "sha256:ac4b75e898ed189b3708c9ab3fc70b79a433219e1e87193b4f2b77251d058d14"},
+    {file = "ruff-0.6.4.tar.gz", hash = "sha256:ac3b5bfbee99973f80aa1b7cbd1c9cbce200883bdd067300c22a6cc1c7fba212"},
 ]
 
 [[package]]
@@ -2537,7 +2538,7 @@ files = [
 
 [[package]]
 name = "scikit-learn"
-version = "1.5.1"
+version = "1.5.2"
 requires_python = ">=3.9"
 summary = "A set of python modules for machine learning and data mining"
 groups = ["default"]
@@ -2548,22 +2549,22 @@ dependencies = [
     "threadpoolctl>=3.1.0",
 ]
 files = [
-    {file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"},
-    {file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"},
-    {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"},
-    {file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"},
-    {file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"},
-    {file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"},
-    {file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"},
-    {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"},
-    {file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"},
-    {file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"},
-    {file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"},
-    {file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"},
-    {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"},
-    {file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"},
-    {file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"},
-    {file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"},
+    {file = "scikit_learn-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:299406827fb9a4f862626d0fe6c122f5f87f8910b86fe5daa4c32dcd742139b6"},
+    {file = "scikit_learn-1.5.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d4cad1119c77930b235579ad0dc25e65c917e756fe80cab96aa3b9428bd3fb0"},
+    {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c412ccc2ad9bf3755915e3908e677b367ebc8d010acbb3f182814524f2e5540"},
+    {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a686885a4b3818d9e62904d91b57fa757fc2bed3e465c8b177be652f4dd37c8"},
+    {file = "scikit_learn-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:c15b1ca23d7c5f33cc2cb0a0d6aaacf893792271cddff0edbd6a40e8319bc113"},
+    {file = "scikit_learn-1.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03b6158efa3faaf1feea3faa884c840ebd61b6484167c711548fce208ea09445"},
+    {file = "scikit_learn-1.5.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1ff45e26928d3b4eb767a8f14a9a6efbf1cbff7c05d1fb0f95f211a89fd4f5de"},
+    {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f763897fe92d0e903aa4847b0aec0e68cadfff77e8a0687cabd946c89d17e675"},
+    {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8b0ccd4a902836493e026c03256e8b206656f91fbcc4fde28c57a5b752561f1"},
+    {file = "scikit_learn-1.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6c16d84a0d45e4894832b3c4d0bf73050939e21b99b01b6fd59cbb0cf39163b6"},
+    {file = "scikit_learn-1.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f932a02c3f4956dfb981391ab24bda1dbd90fe3d628e4b42caef3e041c67707a"},
+    {file = "scikit_learn-1.5.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3b923d119d65b7bd555c73be5423bf06c0105678ce7e1f558cb4b40b0a5502b1"},
+    {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"},
+    {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"},
+    {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"},
+    {file = "scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d"},
 ]
 
 [[package]]
@@ -2615,13 +2616,13 @@ files = [
 
 [[package]]
 name = "setuptools"
-version = "73.0.0"
+version = "74.1.2"
 requires_python = ">=3.8"
 summary = "Easily download, build, install, upgrade, and uninstall Python packages"
 groups = ["default"]
 files = [
-    {file = "setuptools-73.0.0-py3-none-any.whl", hash = "sha256:f2bfcce7ae1784d90b04c57c2802e8649e1976530bb25dc72c2b078d3ecf4864"},
-    {file = "setuptools-73.0.0.tar.gz", hash = "sha256:3c08705fadfc8c7c445cf4d98078f0fafb9225775b2b4e8447e40348f82597c0"},
+    {file = "setuptools-74.1.2-py3-none-any.whl", hash = "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308"},
+    {file = "setuptools-74.1.2.tar.gz", hash = "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6"},
 ]
 
 [[package]]
diff --git a/siapy/core/configs.py b/siapy/core/configs.py
index 717b440..8cbc171 100644
--- a/siapy/core/configs.py
+++ b/siapy/core/configs.py
@@ -1,5 +1,11 @@
 from pathlib import Path
 
+__all__ = [
+    "BASE_DIR",
+    "SIAPY_DIR",
+    "TEST_DATA_DIR",
+]
+
 BASE_DIR = Path(__file__).parent.parent.parent.absolute()
 SIAPY_DIR = Path(BASE_DIR, "siapy")
 TEST_DATA_DIR = Path(BASE_DIR, "tests", "data")
diff --git a/siapy/core/exceptions.py b/siapy/core/exceptions.py
new file mode 100644
index 0000000..5178272
--- /dev/null
+++ b/siapy/core/exceptions.py
@@ -0,0 +1,96 @@
+from typing import Any
+
+__all__ = [
+    "SiapyError",
+    "InvalidFilepathError",
+    "InvalidInputError",
+    "InvalidTypeError",
+    "ProcessingError",
+    "ConfigurationError",
+    "MethodNotImplementedError",
+    "DirectInitializationError",
+]
+
+
+class SiapyError(Exception):
+    """Base exception for SiaPy library."""
+
+    def __init__(self, message: str, name: str = "SiaPy") -> None:
+        self.message: str = message
+        self.name: str = name
+        super().__init__(self.message, self.name)
+
+
+class InvalidFilepathError(SiapyError):
+    """Exception raised when a required file is not found."""
+
+    def __init__(self, filename: str) -> None:
+        self.filename: str = filename
+        super().__init__(f"File not found: {filename}")
+
+
+class InvalidInputError(SiapyError):
+    """Exception raised for invalid input."""
+
+    def __init__(self, input_value: Any, message: str = "Invalid input") -> None:
+        self.input_value: Any = input_value
+        self.message: str = message
+        super().__init__(f"{message}: {input_value}")
+
+
+class InvalidTypeError(SiapyError):
+    """Exception raised for invalid type."""
+
+    def __init__(
+        self,
+        input_value: Any,
+        allowed_types: type | tuple[type, ...],
+        message: str = "Invalid type",
+    ) -> None:
+        self.input_value: Any = input_value
+        self.input_type: Any = type(input_value)
+        self.allowed_types: type | tuple[type, ...] = allowed_types
+        self.message: str = message
+        super().__init__(
+            f"{message}: {input_value} (type: {self.input_type}). Allowed types: {allowed_types}"
+        )
+
+
+class ProcessingError(SiapyError):
+    """Exception raised for errors during processing."""
+
+    def __init__(self, message: str = "An error occurred during processing") -> None:
+        self.message: str = message
+        super().__init__(message)
+
+
+class ConfigurationError(SiapyError):
+    """Exception raised for configuration errors."""
+
+    def __init__(self, message: str = "Configuration error") -> None:
+        self.message: str = message
+        super().__init__(message)
+
+
+class MethodNotImplementedError(SiapyError):
+    """Exception raised for not implemented methods."""
+
+    def __init__(self, class_name: str, method_name: str) -> None:
+        self.class_name: str = class_name
+        self.method_name: str = method_name
+        super().__init__(
+            f"Method '{method_name}' not implemented in class '{class_name}'"
+        )
+
+
+class DirectInitializationError(SiapyError):
+    """Exception raised when a class method is required to create an instance."""
+
+    def __init__(self, class_: type) -> None:
+        from siapy.utils.general import get_classmethods
+
+        self.class_name: str = class_.__class__.__name__
+        self.class_methods: list[str] = get_classmethods(class_)
+        super().__init__(
+            f"Use any of the @classmethod to create a new instance of '{self.class_name}': {self.class_methods}"
+        )
diff --git a/siapy/core/logger.py b/siapy/core/logger.py
index 4ac3c02..96277a5 100644
--- a/siapy/core/logger.py
+++ b/siapy/core/logger.py
@@ -1,3 +1,5 @@
 import logging
 
+__all__ = ["logger"]
+
 logger = logging.getLogger("siapy")
diff --git a/siapy/core/types.py b/siapy/core/types.py
index 3bfe698..c87e8bf 100644
--- a/siapy/core/types.py
+++ b/siapy/core/types.py
@@ -1,11 +1,22 @@
+from typing import Any, Sequence
+
 import numpy as np
+import pandas as pd
 import spectral as sp
 from PIL.Image import Image
-import pandas as pd
-from typing import Sequence, Any
 
 from siapy.entities import SpectralImage, SpectralImageSet
 
+__all__ = [
+    "SpectralType",
+    "ImageType",
+    "ImageSizeType",
+    "ImageDataType",
+    "ImageContainerType",
+    "ArrayLike1dType",
+    "ArrayLike2dType",
+]
+
 SpectralType = sp.io.envi.BilFile | sp.io.envi.BipFile | sp.io.envi.BsqFile
 ImageType = SpectralImage | np.ndarray | Image
 ImageSizeType = int | tuple[int, int]
diff --git a/siapy/datasets/helpers.py b/siapy/datasets/helpers.py
index ddc755c..234f082 100644
--- a/siapy/datasets/helpers.py
+++ b/siapy/datasets/helpers.py
@@ -5,6 +5,12 @@
 if TYPE_CHECKING:
     from .schemas import ClassificationTarget, RegressionTarget, TabularDatasetData
 
+__all__ = [
+    "generate_classification_target",
+    "generate_regression_target",
+    "merge_signals_from_multiple_cameras",
+]
+
 
 def generate_classification_target(
     dataframe: pd.DataFrame,
diff --git a/siapy/datasets/schemas.py b/siapy/datasets/schemas.py
index c464dac..e54560e 100644
--- a/siapy/datasets/schemas.py
+++ b/siapy/datasets/schemas.py
@@ -4,8 +4,16 @@
 import pandas as pd
 from pydantic import BaseModel, ConfigDict
 
+from siapy.core.exceptions import InvalidInputError
+
 from .helpers import generate_classification_target, generate_regression_target
 
+__all__ = [
+    "ClassificationTarget",
+    "RegressionTarget",
+    "TabularDatasetData",
+]
+
 
 class Target(BaseModel, ABC):
     model_config = ConfigDict(arbitrary_types_allowed=True)
@@ -163,14 +171,25 @@ def target_from_dict(data: dict[str, Any] | None) -> Target | None:
         elif data_keys.issubset(classification_keys):
             return ClassificationTarget.from_dict(data)
         else:
-            raise ValueError("Invalid target dict.")
+            raise InvalidInputError(data, "Invalid target dict.")
 
     def _validate_lengths(self) -> None:
         if not (len(self.pixels) == len(self.signals) == len(self.metadata)):
-            raise ValueError("Lengths of pixels, signals, and metadata must be equal")
+            raise InvalidInputError(
+                {
+                    "pixels_length": len(self.pixels),
+                    "signals_length": len(self.signals),
+                    "metadata_length": len(self.metadata),
+                },
+                "Lengths of pixels, signals, and metadata must be equal",
+            )
         if self.target is not None and len(self.target) != len(self):
-            raise ValueError(
-                "Target length must be equal to the length of the dataset."
+            raise InvalidInputError(
+                {
+                    "target_length": len(self.target),
+                    "dataset_length": len(self),
+                },
+                "Target length must be equal to the length of the dataset.",
             )
 
     def to_dict(self) -> dict[str, Any]:
diff --git a/siapy/datasets/tabular.py b/siapy/datasets/tabular.py
index 64426a6..d8bb996 100644
--- a/siapy/datasets/tabular.py
+++ b/siapy/datasets/tabular.py
@@ -5,10 +5,15 @@
 import pandas as pd
 from pydantic import BaseModel, ConfigDict
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.core.types import ImageContainerType
 from siapy.datasets.schemas import TabularDatasetData
 from siapy.entities import Signatures, SpectralImage, SpectralImageSet
 
+__all__ = [
+    "TabularDataset",
+]
+
 
 class MetaDataEntity(BaseModel):
     image_idx: int
@@ -112,7 +117,10 @@ def generate_dataset_data(self, mean_signatures=True) -> TabularDatasetData:
 
     def _check_data_entities(self):
         if not self.data_entities:
-            raise ValueError(
-                f"No data_entities! You need to process image set first."
-                f"by running {self.process_image_data.__name__}() function."
+            raise InvalidInputError(
+                {
+                    "data_entities": self.data_entities,
+                    "required_action": f"Run {self.process_image_data.__name__}() to process image set.",
+                },
+                "No data_entities! You need to process the image set first.",
             )
diff --git a/siapy/entities/images.py b/siapy/entities/images.py
index 173256f..a39f559 100644
--- a/siapy/entities/images.py
+++ b/siapy/entities/images.py
@@ -8,6 +8,8 @@
 import spectral as sp
 from PIL import Image, ImageOps
 
+from siapy.core.exceptions import InvalidFilepathError, InvalidInputError
+
 from .shapes import Shape
 from .signatures import Signatures
 
@@ -16,6 +18,12 @@
     from .pixels import Pixels
 
 
+__all__ = [
+    "GeometricShapes",
+    "SpectralImage",
+]
+
+
 @dataclass
 class GeometricShapes:
     def __init__(
@@ -101,12 +109,25 @@ def get_by_name(self, name: str) -> Shape | None:
     def _check_shape_type(self, shapes: Shape | Iterable[Shape]):
         if isinstance(shapes, Shape):
             return
+
         if not isinstance(shapes, Iterable):
-            raise ValueError(
-                "Shapes must be an instance of Shape or an iterable of Shape instances."
+            raise InvalidInputError(
+                {
+                    "shapes_type": type(shapes).__name__,
+                },
+                "Shapes must be an instance of Shape or an iterable of Shape instances.",
             )
         if not all(isinstance(shape, Shape) for shape in shapes):
-            raise ValueError("All items must be instances of Shape subclass.")
+            raise InvalidInputError(
+                {
+                    "invalid_items": [
+                        type(shape).__name__
+                        for shape in shapes
+                        if not isinstance(shape, Shape)
+                    ],
+                },
+                "All items must be instances of Shape subclass.",
+            )
 
 
 @dataclass
@@ -138,9 +159,16 @@ def __eq__(self, other: Any) -> bool:
     def envi_open(
         cls, *, header_path: str | Path, image_path: str | Path | None = None
     ) -> "SpectralImage":
+        if not Path(header_path).exists():
+            raise InvalidFilepathError(str(header_path))
         sp_file = sp.envi.open(file=header_path, image=image_path)
         if isinstance(sp_file, sp.io.envi.SpectralLibrary):
-            raise ValueError("Opened file of type SpectralLibrary")
+            raise InvalidInputError(
+                {
+                    "file_type": type(sp_file).__name__,
+                },
+                "Opened file of type SpectralLibrary",
+            )
         return cls(sp_file)
 
     @property
@@ -268,9 +296,28 @@ def _parse():
 
     try:
         return _parse()
+
     except ValueError as e:
-        raise ValueError(f"Error parsing description: {e}") from e
+        raise InvalidInputError(
+            {
+                "description": description,
+                "error": str(e),
+            },
+            f"Error parsing description: {e}",
+        ) from e
     except KeyError as e:
-        raise KeyError(f"Missing key in description: {e}") from e
+        raise InvalidInputError(
+            {
+                "description": description,
+                "error": str(e),
+            },
+            f"Missing key in description: {e}",
+        ) from e
     except Exception as e:
-        raise Exception(f"Unexpected error parsing description: {e}") from e
+        raise InvalidInputError(
+            {
+                "description": description,
+                "error": str(e),
+            },
+            f"Unexpected error parsing description: {e}",
+        ) from e
diff --git a/siapy/entities/imagesets.py b/siapy/entities/imagesets.py
index 8242906..f04e2ff 100644
--- a/siapy/entities/imagesets.py
+++ b/siapy/entities/imagesets.py
@@ -6,9 +6,14 @@
 from rich.progress import track
 
 from siapy.core import logger
+from siapy.core.exceptions import InvalidInputError
 
 from .images import SpectralImage
 
+__all__ = [
+    "SpectralImageSet",
+]
+
 
 @dataclass
 class SpectralImageSet:
@@ -35,7 +40,13 @@ def from_paths(
         image_paths: Sequence[str | Path] | None = None,
     ):
         if image_paths is not None and len(header_paths) != len(image_paths):
-            raise ValueError("The length of hdr_paths and img_path must be equal.")
+            raise InvalidInputError(
+                {
+                    "header_paths_length": len(header_paths),
+                    "image_paths_length": len(image_paths),
+                },
+                "The length of hdr_paths and img_path must be equal.",
+            )
 
         if image_paths is None:
             spectral_images = [
diff --git a/siapy/entities/pixels.py b/siapy/entities/pixels.py
index 6dc877d..04f3ca5 100644
--- a/siapy/entities/pixels.py
+++ b/siapy/entities/pixels.py
@@ -5,6 +5,10 @@
 import numpy as np
 import pandas as pd
 
+__all__ = [
+    "Pixels",
+]
+
 
 class Coordinates(NamedTuple):
     U: Annotated[str, "u - x coordinate on the image"] = "u"
diff --git a/siapy/entities/shapes.py b/siapy/entities/shapes.py
index e1edb5d..bc4b0ec 100644
--- a/siapy/entities/shapes.py
+++ b/siapy/entities/shapes.py
@@ -5,8 +5,17 @@
 import numpy as np
 from matplotlib.path import Path
 
+from siapy.core.exceptions import InvalidInputError, MethodNotImplementedError
+
 from .pixels import Pixels
 
+__all__ = [
+    "Shape",
+    "Rectangle",
+    "Point",
+    "FreeDraw",
+]
+
 SHAPE_TYPE_RECTANGLE = "rectangle"
 SHAPE_TYPE_POINT = "point"
 SHAPE_TYPE_FREEDRAW = "freedraw"
@@ -44,7 +53,12 @@ def from_shape_type(
                 label=label,
             )
         else:
-            raise ValueError(f"Unsupported shape type: {shape_type}")
+            raise InvalidInputError(
+                {
+                    "shape_type": shape_type,
+                },
+                f"Unsupported shape type: {shape_type}",
+            )
 
     @property
     def shape_type(self) -> str:
@@ -60,9 +74,7 @@ def label(self) -> str | None:
 
     @abstractmethod
     def convex_hull(self):
-        raise NotImplementedError(
-            "convex_hull() method is not implemented for the base Shape class."
-        )
+        raise MethodNotImplementedError(self.__class__.__name__, "convex_hull")
 
 
 class Rectangle(Shape):
diff --git a/siapy/entities/signatures.py b/siapy/entities/signatures.py
index a8a88a3..201c358 100644
--- a/siapy/entities/signatures.py
+++ b/siapy/entities/signatures.py
@@ -5,10 +5,15 @@
 import numpy as np
 import pandas as pd
 
-from siapy.utils.general import get_classmethods
+from siapy.core.exceptions import DirectInitializationError, InvalidInputError
 
 from .pixels import Pixels
 
+__all__ = [
+    "Signatures",
+    "Signals",
+]
+
 
 @dataclass
 class Signals:
@@ -61,9 +66,7 @@ class Signatures:
     _signals: Signals
 
     def __init__(self, *args: Any, **kwargs: Any):
-        raise RuntimeError(
-            f"Use any of the @classmethod to create a new instance: {get_classmethods(Signatures)}"
-        )
+        raise DirectInitializationError(Signatures)
 
     @classmethod
     def _create(cls, pixels: Pixels, signals: Signals) -> "Signatures":
@@ -85,9 +88,9 @@ def from_dataframe(cls, dataframe: pd.DataFrame) -> "Signatures":
         if not all(
             coord in dataframe.columns for coord in [Pixels.coords.U, Pixels.coords.V]
         ):
-            raise ValueError(
-                f"DataFrame must include columns for both '{Pixels.coords.U}'"
-                f" and '{Pixels.coords.V}' coordinates."
+            raise InvalidInputError(
+                dataframe.columns.tolist(),
+                f"DataFrame must include columns for both '{Pixels.coords.U}' and '{Pixels.coords.V}' coordinates.",
             )
         pixels = Pixels(dataframe[[Pixels.coords.U, Pixels.coords.V]])
         signals = Signals(dataframe.drop(columns=[Pixels.coords.U, Pixels.coords.V]))
diff --git a/siapy/features/features.py b/siapy/features/features.py
index 6221203..e9bdb2c 100644
--- a/siapy/features/features.py
+++ b/siapy/features/features.py
@@ -5,10 +5,18 @@
 from autofeat import AutoFeatClassifier, AutoFeatRegressor  # type: ignore
 from sklearn.base import BaseEstimator, TransformerMixin
 
+from siapy.core.exceptions import MethodNotImplementedError
 from siapy.features.helpers import FeatureSelectorConfig, feature_selector_factory
 from siapy.features.spectral_indices import compute_spectral_indices
 from siapy.utils.general import set_random_seed
 
+__all__ = [
+    "AutoFeatClassification",
+    "AutoFeatRegression",
+    "AutoSpectralIndicesClassification",
+    "AutoSpectralIndicesRegression",
+]
+
 
 class AutoFeatClassification(AutoFeatClassifier):
     def __init__(
@@ -142,8 +150,8 @@ def transform(self, data: pd.DataFrame) -> pd.DataFrame:
             columns_select_idx = list(self.selector[1].k_feature_idx_)
             df_indices = df_indices.iloc[:, columns_select_idx]
         else:
-            raise AttributeError(
-                "The attribute 'k_feature_idx_' does not exist in the selector."
+            raise MethodNotImplementedError(
+                self.selector[1].__class__.__name__, "k_feature_idx_"
             )
         if self.merge_with_original:
             return pd.concat([data, df_indices], axis=1)
diff --git a/siapy/features/helpers.py b/siapy/features/helpers.py
index 74d0605..0294972 100644
--- a/siapy/features/helpers.py
+++ b/siapy/features/helpers.py
@@ -6,6 +6,13 @@
 from sklearn.pipeline import Pipeline, make_pipeline
 from sklearn.preprocessing import RobustScaler
 
+from siapy.core.exceptions import InvalidInputError
+
+__all__ = [
+    "FeatureSelectorConfig",
+    "feature_selector_factory",
+]
+
 
 class FeatureSelectorConfig(BaseModel):
     k_features: Annotated[
@@ -63,8 +70,9 @@ def feature_selector_factory(
         algo = RidgeClassifier()
         scoring = "f1_weighted"
     else:
-        raise ValueError(
-            f"Invalid problem type: '{problem_type}', possible values are: 'regression' or 'classification'"
+        raise InvalidInputError(
+            problem_type,
+            "Invalid problem type, possible values are: 'regression' or 'classification'",
         )
     sfs = SequentialFeatureSelector(
         estimator=algo,
diff --git a/siapy/features/spectral_indices.py b/siapy/features/spectral_indices.py
index b8dfeb3..f53aa7a 100644
--- a/siapy/features/spectral_indices.py
+++ b/siapy/features/spectral_indices.py
@@ -4,6 +4,8 @@
 import numpy as np
 import pandas as pd
 
+from siapy.core.exceptions import InvalidInputError
+
 with warnings.catch_warnings():
     warnings.filterwarnings(
         "ignore",
@@ -12,6 +14,11 @@
     )
     import spyndex  # type: ignore
 
+__all__ = [
+    "get_spectral_indices",
+    "compute_spectral_indices",
+]
+
 
 def _convert_str_to_list(bands_acronym: Any) -> Any:
     if isinstance(bands_acronym, str):
@@ -26,11 +33,12 @@ def get_spectral_indices(
     bands_acronym_set = set(bands_acronym)
 
     if not bands_acronym_set.issubset(list(spyndex.bands)):
-        raise ValueError(
-            f"Invalid input argument for 'bands_acronym'. \n"
-            f"Received: {bands_acronym_set}. \n"
-            f"Possible options are: {list(spyndex.bands)}. \n"
-            "Please ensure that all elements in 'bands_acronym' are valid band acronyms."
+        raise InvalidInputError(
+            {
+                "received_bands_acronym": bands_acronym_set,
+                "valid_bands_acronym": list(spyndex.bands),
+            },
+            "Invalid input argument for 'bands_acronym'. Please ensure that all elements in 'bands_acronym' are valid band acronyms.",
         )
 
     spectral_indexes = {}
@@ -54,19 +62,25 @@ def compute_spectral_indices(
     for band in data.columns:
         if bands_map is not None and band in bands_map.keys():
             if bands_map[band] not in list(spyndex.bands):
-                raise ValueError(
-                    f"Invalid band mapping: '{bands_map[band]}' is not a recognized band acronym. \n"
+                raise InvalidInputError(
+                    {
+                        "received_band_mapping": bands_map[band],
+                        "valid_bands_acronym": list(spyndex.bands),
+                    },
+                    f"Invalid band mapping is not a recognized band acronym. \n"
                     f"Received mapping: {band} -> {bands_map[band]}. \n"
-                    f"Possible options are: {list(spyndex.bands)}. \n"
-                    "Please ensure that all values in 'bands_map' are valid band acronyms."
+                    "Please ensure that all values in 'bands_map' are valid band acronyms.",
                 )
             params[bands_map[band]] = data[band]
         else:
             if band not in list(spyndex.bands):
-                raise ValueError(
+                raise InvalidInputError(
+                    {
+                        "received_band": band,
+                        "valid_bands_acronym": list(spyndex.bands),
+                    },
                     f"Invalid band: '{band}' is not a recognized band acronym. \n"
-                    f"Possible options are: {list(spyndex.bands)}. \n"
-                    "Please ensure that all columns in 'data' are valid band acronyms."
+                    "Please ensure that all columns in 'data' are valid band acronyms.",
                 )
             params[band] = data[band]
 
diff --git a/siapy/models/metrics.py b/siapy/models/metrics.py
index ad74329..5e78c0b 100644
--- a/siapy/models/metrics.py
+++ b/siapy/models/metrics.py
@@ -14,6 +14,13 @@
     recall_score,
 )
 
+from siapy.core.exceptions import InvalidInputError
+
+__all__ = [
+    "calculate_classification_metrics",
+    "calculate_regression_metrics",
+]
+
 
 def normalized_RMSE(
     y_true: np.ndarray,
@@ -26,7 +33,10 @@ def normalized_RMSE(
     elif normalize_by == "mean":
         normalizer = np.mean(y_true)
     else:
-        raise ValueError(f"Unknown normalizer {normalize_by}")
+        raise InvalidInputError(
+            input_value=normalize_by,
+            message="Unknown normalizer. Possible values are: 'range' or 'mean'.",
+        )
     return rmse / normalizer
 
 
diff --git a/siapy/optimizers/configs.py b/siapy/optimizers/configs.py
index 95ee95a..25105b7 100644
--- a/siapy/optimizers/configs.py
+++ b/siapy/optimizers/configs.py
@@ -6,6 +6,12 @@
 from siapy.optimizers.parameters import TrialParameters
 from siapy.optimizers.scorers import Scorer
 
+__all__ = [
+    "CreateStudyConfig",
+    "OptimizeStudyConfig",
+    "TabularOptimizerConfig",
+]
+
 
 class CreateStudyConfig(BaseModel):
     model_config = ConfigDict(arbitrary_types_allowed=True)
diff --git a/siapy/optimizers/optimizers.py b/siapy/optimizers/optimizers.py
index bfc552e..0c9056b 100644
--- a/siapy/optimizers/optimizers.py
+++ b/siapy/optimizers/optimizers.py
@@ -4,6 +4,7 @@
 from sklearn.base import BaseEstimator, clone
 
 from siapy.core import logger
+from siapy.core.exceptions import InvalidInputError
 from siapy.core.types import ArrayLike1dType, ArrayLike2dType
 from siapy.datasets.schemas import TabularDatasetData
 from siapy.optimizers.configs import TabularOptimizerConfig
@@ -13,6 +14,10 @@
     IntParameter,
 )
 
+__all__ = [
+    "TabularOptimizer",
+]
+
 
 class TabularOptimizer:
     def __init__(
@@ -47,11 +52,16 @@ def from_tabular_dataset_data(
         target_val = data_val.target if data_val else None
 
         if target is None:
-            raise ValueError("Target data is required for optimization.")
+            raise InvalidInputError(
+                input_value=target, message="Target data is required for optimization."
+            )
         if signals_val is not None and target_val is None:
-            raise ValueError(
-                "If validation data (data_val) is provided, "
-                "validation targets (data_val.target) must also be provided."
+            raise InvalidInputError(
+                input_value={"signals_val": signals_val, "target_val": target_val},
+                message=(
+                    "If validation data (data_val) is provided, "
+                    "validation targets (data_val.target) must also be provided."
+                ),
             )
         return cls(
             model=model,
@@ -84,7 +94,10 @@ def run(self) -> optuna.study.Study:
 
     def get_best_model(self) -> BaseEstimator:
         if self.best_trial is None:
-            raise ValueError("Study is not available for model refitting.")
+            raise InvalidInputError(
+                input_value="None",
+                message="Study is not available for model refitting.",
+            )
 
         best_model = clone(self.model)
         best_model.set_params(**self.best_trial.params)
@@ -100,9 +113,10 @@ def objective(self, trial: optuna.trial.Trial) -> float:
 
     def _trial_params(self, trial: optuna.trial.Trial) -> dict[str, Any]:
         if self.configs.trial_parameters is None:
-            raise ValueError(
-                "Trial parameters are not defined. "
-                "Add trial_parameters to configs or implement your custom objective function."
+            raise InvalidInputError(
+                input_value="None",
+                message="Trial parameters are not defined. "
+                "Add trial_parameters to configs or implement your custom objective function.",
             )
 
         params: dict[str, Any] = {}
@@ -132,8 +146,9 @@ def _trial_params(self, trial: optuna.trial.Trial) -> dict[str, Any]:
 
     def scorer(self) -> float:
         if self.configs.scorer is None:
-            raise ValueError(
-                "Scorer is not defined. Add scorer to configs or implement your custom scorer."
+            raise InvalidInputError(
+                input_value=self.configs.scorer,
+                message="Scorer is not defined. Add scorer to configs or implement your custom scorer.",
             )
         return self.configs.scorer(
             model=self.model,
diff --git a/siapy/optimizers/parameters.py b/siapy/optimizers/parameters.py
index f59183e..b9a35cb 100644
--- a/siapy/optimizers/parameters.py
+++ b/siapy/optimizers/parameters.py
@@ -3,6 +3,13 @@
 
 from pydantic import BaseModel
 
+__all__ = [
+    "FloatParameter",
+    "IntParameter",
+    "CategoricalParameter",
+    "TrialParameters",
+]
+
 
 class FloatParameter(BaseModel):
     name: str
diff --git a/siapy/optimizers/scorers.py b/siapy/optimizers/scorers.py
index 3613535..e1b8e76 100644
--- a/siapy/optimizers/scorers.py
+++ b/siapy/optimizers/scorers.py
@@ -9,6 +9,10 @@
 from siapy.utils.evaluators import ScorerFuncType, cross_validation, hold_out_validation
 from siapy.utils.general import initialize_object
 
+__all__ = [
+    "Scorer",
+]
+
 
 class Scorer:
     def __init__(self, scorer):
diff --git a/siapy/py.typed b/siapy/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/siapy/transformations/corregistrator.py b/siapy/transformations/corregistrator.py
index ac4107c..4efad7b 100644
--- a/siapy/transformations/corregistrator.py
+++ b/siapy/transformations/corregistrator.py
@@ -3,6 +3,13 @@
 
 from siapy.entities.pixels import Pixels
 
+__all__ = [
+    "map_affine_approx_2d",
+    "affine_matx_2d",
+    "align",
+    "transform",
+]
+
 
 def map_affine_approx_2d(points_ref: np.ndarray, points_mov: np.ndarray) -> np.ndarray:
     """Affine transformation"""
diff --git a/siapy/transformations/image.py b/siapy/transformations/image.py
index 71ba298..76af609 100644
--- a/siapy/transformations/image.py
+++ b/siapy/transformations/image.py
@@ -7,6 +7,15 @@
 from siapy.core.types import ImageSizeType, ImageType
 from siapy.utils.validators import validate_image_size, validate_image_to_numpy
 
+__all__ = [
+    "add_gaussian_noise",
+    "random_crop",
+    "random_mirror",
+    "random_rotation",
+    "rescale",
+    "area_normalization",
+]
+
 
 def add_gaussian_noise(
     image: ImageType,
diff --git a/siapy/utils/enums.py b/siapy/utils/enums.py
index fa1ff70..4bc990c 100644
--- a/siapy/utils/enums.py
+++ b/siapy/utils/enums.py
@@ -1,5 +1,9 @@
 from enum import Enum, auto
 
+__all__ = [
+    "InteractiveButtonsEnum",
+]
+
 
 class InteractiveButtonsEnum(Enum):
     SAVE = auto()
diff --git a/siapy/utils/evaluators.py b/siapy/utils/evaluators.py
index 87dd863..2474ac2 100644
--- a/siapy/utils/evaluators.py
+++ b/siapy/utils/evaluators.py
@@ -10,10 +10,16 @@
 )
 
 from siapy.core import logger
+from siapy.core.exceptions import InvalidInputError
 from siapy.core.types import ArrayLike1dType, ArrayLike2dType
 
 from .validators import check_model_prediction_methods
 
+__all__ = [
+    "cross_validation",
+    "hold_out_validation",
+]
+
 ScorerFuncType = Callable[[BaseEstimator, ArrayLike2dType, ArrayLike1dType], float]
 
 
@@ -71,8 +77,9 @@ def hold_out_validation(
     if X_val is not None and y_val is not None:
         x_train, x_test, y_train, y_test = X, X_val, y, y_val
     elif X_val is not None or y_val is not None:
-        raise ValueError(
-            "To manually define validation set, both X_val and y_val must be specified."
+        raise InvalidInputError(
+            input_value={"X_val": X_val, "y_val": y_val},
+            message="To manually define validation set, both X_val and y_val must be specified.",
         )
     else:
         x_train, x_test, y_train, y_test = train_test_split(
diff --git a/siapy/utils/general.py b/siapy/utils/general.py
index c013e01..2ea709a 100644
--- a/siapy/utils/general.py
+++ b/siapy/utils/general.py
@@ -10,6 +10,19 @@
 import numpy as np
 
 from siapy.core import logger
+from siapy.core.exceptions import InvalidInputError
+
+__all__ = [
+    "initialize_object",
+    "initialize_function",
+    "ensure_dir",
+    "get_number_cpus",
+    "dict_zip",
+    "get_increasing_seq_indices",
+    "set_random_seed",
+    "get_classmethods",
+    "match_iterable_items_by_regex",
+]
 
 
 def initialize_object(
@@ -58,7 +71,9 @@ def get_number_cpus(parallelize: int = -1) -> int:
     elif parallelize > num_cpus:
         parallelize = num_cpus
     else:
-        raise ValueError("Define accurate number of cpus")
+        raise InvalidInputError(
+            input_value=parallelize, message="Define accurate number of CPUs."
+        )
     return parallelize
 
 
@@ -68,7 +83,9 @@ def dict_zip(*dicts: dict[str, Any]) -> Generator[tuple[str, Any, Any], None, No
 
     n = len(dicts[0])
     if any(len(d) != n for d in dicts):
-        raise ValueError("arguments must have the same length")
+        raise InvalidInputError(
+            input_value=dicts, message="Arguments must have the same length."
+        )
 
     for key, first_val in dicts[0].items():
         yield key, first_val, *(other[key] for other in dicts[1:])
diff --git a/siapy/utils/images.py b/siapy/utils/images.py
index 80a9bca..80323fe 100644
--- a/siapy/utils/images.py
+++ b/siapy/utils/images.py
@@ -11,6 +11,16 @@
 from siapy.transformations.image import rescale
 from siapy.utils.validators import validate_image_to_numpy
 
+__all__ = [
+    "save_image",
+    "create_image",
+    "merge_images_by_specter",
+    "convert_radiance_image_to_reflectance",
+    "calculate_correction_factor_from_panel",
+    "blockfy_image",
+    "calculate_image_background_percentage",
+]
+
 
 def save_image(
     image: Annotated[np.ndarray, "The image to save."],
diff --git a/siapy/utils/plots.py b/siapy/utils/plots.py
index 36ac5ee..28b564f 100644
--- a/siapy/utils/plots.py
+++ b/siapy/utils/plots.py
@@ -8,6 +8,7 @@
 from matplotlib.path import Path
 from matplotlib.widgets import Button, LassoSelector
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.core.logger import logger
 from siapy.core.types import ImageType
 from siapy.datasets.schemas import ClassificationTarget, TabularDatasetData
@@ -15,6 +16,14 @@
 from siapy.utils.enums import InteractiveButtonsEnum
 from siapy.utils.validators import validate_image_to_numpy_3channels
 
+__all__ = [
+    "pixels_select_click",
+    "pixels_select_lasso",
+    "display_image_with_areas",
+    "display_multiple_images_with_areas",
+    "display_signals",
+]
+
 
 def pixels_select_click(image: ImageType) -> Pixels:
     image_display = validate_image_to_numpy_3channels(image)
@@ -234,7 +243,10 @@ def display_signals(
     legend_frameon: bool = True,
 ):
     if not isinstance(data.target, ClassificationTarget):
-        raise ValueError("The target must be an instance of ClassificationTarget.")
+        raise InvalidInputError(
+            input_value=data.target,
+            message="The target must be an instance of ClassificationTarget.",
+        )
 
     signals = data.signals.copy()
     target = data.target.model_copy()
diff --git a/siapy/utils/validators.py b/siapy/utils/validators.py
index 9fe58c7..78d2732 100644
--- a/siapy/utils/validators.py
+++ b/siapy/utils/validators.py
@@ -2,9 +2,21 @@
 from PIL.Image import Image
 from sklearn.base import BaseEstimator
 
+from siapy.core.exceptions import (
+    InvalidInputError,
+    InvalidTypeError,
+    MethodNotImplementedError,
+)
 from siapy.core.types import ImageSizeType, ImageType
 from siapy.entities import SpectralImage
 
+__all__ = [
+    "validate_image_to_numpy_3channels",
+    "validate_image_to_numpy",
+    "validate_image_size",
+    "check_model_prediction_methods",
+]
+
 
 def validate_image_to_numpy_3channels(image: ImageType) -> np.ndarray:
     if isinstance(image, SpectralImage):
@@ -16,8 +28,9 @@ def validate_image_to_numpy_3channels(image: ImageType) -> np.ndarray:
     ):
         image_display = image.copy()
     else:
-        raise ValueError(
-            "Argument image must be convertible to numpy array with 3 channels."
+        raise InvalidInputError(
+            input_value=image,
+            message="Argument image must be convertible to numpy array with 3 channels.",
         )
     return image_display
 
@@ -30,28 +43,32 @@ def validate_image_to_numpy(image: ImageType) -> np.ndarray:
     elif isinstance(image, np.ndarray):
         image_np = image.copy()
     else:
-        raise ValueError("Argument image must be convertible to a numpy array.")
+        raise InvalidInputError(
+            input_value=image,
+            message="Argument image must be convertible to a numpy array.",
+        )
     return image_np
 
 
 def validate_image_size(output_size: ImageSizeType) -> tuple[int, int]:
     if not isinstance(output_size, (int, tuple)):
-        raise TypeError("Argument output_size must be an int or a tuple.")
+        raise InvalidTypeError(
+            input_value=output_size,
+            allowed_types=ImageSizeType,
+            message="Argument output_size must be an int or a tuple.",
+        )
     if isinstance(output_size, int):
         output_size = (output_size, output_size)
     elif len(output_size) != 2 or not all([isinstance(el, int) for el in output_size]):
-        raise ValueError(
-            "Argument output_size tuple must have 2 elements and contain only integers."
+        raise InvalidInputError(
+            input_value=output_size,
+            message="Argument output_size tuple must have 2 elements and contain only integers.",
         )
     return output_size
 
 
 def check_model_prediction_methods(model: BaseEstimator):
-    if (
-        not hasattr(model, "fit")
-        or not hasattr(model, "score")
-        or not hasattr(model, "predict")
-    ):
-        raise AttributeError(
-            "The model must have methods: 'fit', 'predict', and 'score'."
-        )
+    required_methods = ["fit", "predict", "score"]
+    for method in required_methods:
+        if not hasattr(model, method):
+            raise MethodNotImplementedError(model.__class__.__name__, method)


Test patch:
diff --git a/tests/datasets/test_datasets_schemas.py b/tests/datasets/test_datasets_schemas.py
index c7c8ade..efd4d66 100644
--- a/tests/datasets/test_datasets_schemas.py
+++ b/tests/datasets/test_datasets_schemas.py
@@ -1,6 +1,7 @@
 import pandas as pd
 import pytest
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.datasets.schemas import (
     ClassificationTarget,
     RegressionTarget,
@@ -270,7 +271,7 @@ def test_tabular_dataset_data_target_from_dict_classification():
 
 def test_tabular_dataset_data_target_from_dict_invalid():
     data = {"unknown_key": "some_value"}
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         TabularDatasetData.target_from_dict(data)
 
 
@@ -304,7 +305,8 @@ def test_tabular_dataset_data_init():
     }
 
     with pytest.raises(
-        ValueError, match="Lengths of pixels, signals, and metadata must be equal"
+        InvalidInputError,
+        match="Lengths of pixels, signals, and metadata must be equal",
     ):
         TabularDatasetData(
             pixels=data["pixels"],
@@ -341,7 +343,8 @@ def test_tabular_dataset_data_setattr():
     # Invalid setattr -> signals
     invalid_signals = pd.DataFrame({"b": [4, 5]})
     with pytest.raises(
-        ValueError, match="Lengths of pixels, signals, and metadata must be equal"
+        InvalidInputError,
+        match="Lengths of pixels, signals, and metadata must be equal",
     ):
         dataset.signals = invalid_signals
 
@@ -349,7 +352,8 @@ def test_tabular_dataset_data_setattr():
     dataset = dataset_.model_copy()
     invalid_target = target[:2]
     with pytest.raises(
-        ValueError, match="Target length must be equal to the length of the dataset."
+        InvalidInputError,
+        match="Target length must be equal to the length of the dataset.",
     ):
         dataset.target = invalid_target
 
diff --git a/tests/entities/test_entities_images.py b/tests/entities/test_entities_images.py
index cb342aa..b465cfc 100644
--- a/tests/entities/test_entities_images.py
+++ b/tests/entities/test_entities_images.py
@@ -5,6 +5,7 @@
 import spectral as sp
 from PIL import Image
 
+from siapy.core.exceptions import InvalidFilepathError, InvalidInputError
 from siapy.entities import Pixels, Shape, SpectralImage
 from siapy.entities.images import GeometricShapes, _parse_description
 from siapy.utils.plots import pixels_select_lasso
@@ -24,6 +25,11 @@ def test_envi_open(configs):
     assert isinstance(spectral_image_swir, SpectralImage)
 
 
+def test_envi_open_invalid():
+    with pytest.raises(InvalidFilepathError):
+        SpectralImage.envi_open(header_path="invalid_header_path")
+
+
 def test_fixture_spectral_images(spectral_images):
     assert isinstance(spectral_images.vnir, SpectralImage)
     assert isinstance(spectral_images.swir, SpectralImage)
@@ -185,8 +191,8 @@ def test_to_signatures(spectral_images):
 def test_to_signatures_perf(spectral_images):
     spectral_image_vnir = spectral_images.vnir
     selected_areas_vnir = pixels_select_lasso(spectral_image_vnir)
-    spectral_image_vnir.to_signatures(selected_areas_vnir[0]).signals.to_numpy()
-    pass
+    out = spectral_image_vnir.to_signatures(selected_areas_vnir[0]).signals.to_numpy()
+    assert isinstance(out, np.ndarray)
 
 
 def test_to_subarray(spectral_images):
@@ -269,7 +275,7 @@ def test_geometric_shapes_setter_invalid(spectral_images):
     invalid_shape = "not a shape"
     shapes = [invalid_shape]
 
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         spectral_images.vnir.geometric_shapes.shapes = shapes
 
 
@@ -305,7 +311,7 @@ def test_geometric_shapes_setitem_valid(spectral_images, corresponding_pixels):
 def test_geometric_shapes_setitem_invalid(spectral_images):
     invalid_shape = "not a shape"
     spectral_images.vnir.geometric_shapes.shapes = []
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         spectral_images.vnir.geometric_shapes[0] = invalid_shape
 
 
@@ -342,7 +348,7 @@ def test_geometric_shapes_append_valid(spectral_images, corresponding_pixels):
 
 def test_geometric_shapes_append_invalid(spectral_images):
     invalid_shape = "not a shape"
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         spectral_images.vnir.geometric_shapes.append(invalid_shape)
 
 
@@ -357,7 +363,7 @@ def test_geometric_shapes_extend_valid(spectral_images, corresponding_pixels):
 
 def test_geometric_shapes_extend_invalid(spectral_images):
     invalid_shape = "not a shape"
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         spectral_images.vnir.geometric_shapes.extend([invalid_shape])
 
 
@@ -371,7 +377,7 @@ def test_geometric_shapes_insert_valid(spectral_images, corresponding_pixels):
 
 def test_geometric_shapes_insert_invalid(spectral_images):
     invalid_shape = "not a shape"
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         spectral_images.vnir.geometric_shapes.insert(0, invalid_shape)
 
 
@@ -387,7 +393,7 @@ def test_geometric_shapes_remove_valid(spectral_images, corresponding_pixels):
 
 def test_geometric_shapes_remove_invalid(spectral_images):
     invalid_shape = "not a shape"
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         spectral_images.vnir.geometric_shapes.remove(invalid_shape)
 
 
@@ -426,7 +432,7 @@ def test_geometric_shapes_index_invalid(spectral_images, corresponding_pixels):
     )
     invalid_shape = "not a shape"
     spectral_images.vnir.geometric_shapes.append(rect)
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         spectral_images.vnir.geometric_shapes.index(invalid_shape)
 
 
@@ -519,5 +525,5 @@ def test_parse_description_empty_value():
 
 def test_parse_description_invalid_format_raises_value_error():
     description = "This is not a valid format"
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         _parse_description(description)
diff --git a/tests/entities/test_entities_imagesets.py b/tests/entities/test_entities_imagesets.py
index 9fc17af..70afce9 100644
--- a/tests/entities/test_entities_imagesets.py
+++ b/tests/entities/test_entities_imagesets.py
@@ -2,6 +2,7 @@
 
 import pytest
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.entities import SpectralImage, SpectralImageSet
 
 
@@ -26,7 +27,7 @@ def test_from_paths_invalid(configs):
         configs.image_swir_hdr_path,
     ]
     image_paths: list[str | Path] = [configs.image_vnir_img_path]
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         SpectralImageSet.from_paths(header_paths=header_paths, image_paths=image_paths)
 
 
diff --git a/tests/entities/test_entities_shapes.py b/tests/entities/test_entities_shapes.py
index ff7edd3..1a6f07d 100644
--- a/tests/entities/test_entities_shapes.py
+++ b/tests/entities/test_entities_shapes.py
@@ -1,6 +1,7 @@
 import numpy as np
 import pytest
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.entities import Pixels, Shape
 from siapy.entities.shapes import (
     SHAPE_TYPE_FREEDRAW,
@@ -34,7 +35,7 @@ def test_from_shape_type():
     freedraw = Shape.from_shape_type(shape_type="freedraw", pixels=pixels)
     assert isinstance(freedraw, FreeDraw)
 
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         Shape.from_shape_type(shape_type="shape", pixels=pixels)
 
 
diff --git a/tests/entities/test_entities_signatures.py b/tests/entities/test_entities_signatures.py
index 9a9c4e2..a80ab1d 100644
--- a/tests/entities/test_entities_signatures.py
+++ b/tests/entities/test_entities_signatures.py
@@ -6,6 +6,7 @@
 import pandas as pd
 import pytest
 
+from siapy.core.exceptions import DirectInitializationError, InvalidInputError
 from siapy.entities import Pixels, Signatures
 from siapy.entities.signatures import Signals, SignaturesFilter
 
@@ -122,7 +123,7 @@ def test_signatures_create():
 
 
 def test_signatures_raise_error():
-    with pytest.raises(RuntimeError):
+    with pytest.raises(DirectInitializationError):
         Signatures()
 
 
@@ -149,12 +150,12 @@ def test_signatures_from_dataframe():
 
     df_missing_u = pd.DataFrame({"V": [0, 1], "0": [1, 2], "1": [3, 4]})
 
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         Signatures.from_dataframe(df_missing_u)
 
     df_missing_v = pd.DataFrame({"U": [0, 1], "0": [1, 2], "1": [3, 4]})
 
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         Signatures.from_dataframe(df_missing_v)
 
 
diff --git a/tests/features/test_features_helpers.py b/tests/features/test_features_helpers.py
index c0f7e77..b36432e 100644
--- a/tests/features/test_features_helpers.py
+++ b/tests/features/test_features_helpers.py
@@ -1,6 +1,7 @@
 import pytest
 from sklearn.pipeline import Pipeline
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.features.helpers import FeatureSelectorConfig, feature_selector_factory
 from tests.utils import assert_pipelines_parameters_equal
 
@@ -29,7 +30,7 @@ def test_feature_selector_factory_classification_pipeline():
 
 
 def test_feature_selector_factory_invalid_problem_type():
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         feature_selector_factory(problem_type="invalid")
 
 
diff --git a/tests/features/test_features_spectral_indices.py b/tests/features/test_features_spectral_indices.py
index 3583da7..ced6e53 100644
--- a/tests/features/test_features_spectral_indices.py
+++ b/tests/features/test_features_spectral_indices.py
@@ -2,6 +2,7 @@
 import pandas as pd
 import pytest
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.features.spectral_indices import (
     _convert_str_to_list,
     compute_spectral_indices,
@@ -27,7 +28,7 @@ def test_get_spectral_indices_valid():
 
 def test_get_spectral_indices_invalid():
     bands_acronym = ["not-correct"]
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         get_spectral_indices(bands_acronym)
 
 
@@ -38,7 +39,7 @@ def test_compute_spectral_indices():
     compute_spectral_indices(data, spectral_indices.keys())
     columns[1] = "not-correct"
     data = pd.DataFrame(np.random.default_rng(seed=0).random((5, 2)), columns=columns)
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         compute_spectral_indices(
             data,
             spectral_indices.keys(),
@@ -50,7 +51,7 @@ def test_compute_spectral_indices_with_map():
         np.random.default_rng(seed=0).random((5, 2)), columns=["R", "not-correct"]
     )
     spectral_indices = get_spectral_indices(["R", "G"])
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         compute_spectral_indices(
             data,
             spectral_indices.keys(),
diff --git a/tests/optimizers/test_optimizers_optimizers.py b/tests/optimizers/test_optimizers_optimizers.py
index aa56c0d..8c3d90c 100644
--- a/tests/optimizers/test_optimizers_optimizers.py
+++ b/tests/optimizers/test_optimizers_optimizers.py
@@ -3,6 +3,7 @@
 from sklearn.base import BaseEstimator
 from sklearn.svm import SVR
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.datasets.schemas import RegressionTarget
 from siapy.optimizers.configs import OptimizeStudyConfig, TabularOptimizerConfig
 from siapy.optimizers.optimizers import TabularOptimizer
@@ -29,7 +30,9 @@ def test_tabular_optimizer_from_tabular_dataset_data_invalid(spectral_tabular_da
     data = spectral_tabular_dataset.dataset_data
     model = SVR()
     configs = TabularOptimizerConfig()
-    with pytest.raises(ValueError, match="Target data is required for optimization"):
+    with pytest.raises(
+        InvalidInputError, match="Target data is required for optimization"
+    ):
         TabularOptimizer.from_tabular_dataset_data(
             model=model, configs=configs, data=data
         )
@@ -44,7 +47,7 @@ def test_tabular_optimizer_with_validation_data_without_targets(
     data.target = target
     model = SVR()
     configs = TabularOptimizerConfig()
-    with pytest.raises(ValueError, match="validation targets"):
+    with pytest.raises(InvalidInputError, match="validation targets"):
         TabularOptimizer.from_tabular_dataset_data(
             model=model, configs=configs, data=data, data_val=data_val
         )
@@ -104,7 +107,9 @@ def test_tabular_optimizer_get_best_model_no_best_trial(spectral_tabular_dataset
     optimizer = TabularOptimizer.from_tabular_dataset_data(
         model=model, configs=configs, data=data
     )
-    with pytest.raises(ValueError, match="Study is not available for model refitting."):
+    with pytest.raises(
+        InvalidInputError, match="Study is not available for model refitting."
+    ):
         optimizer.get_best_model()
 
 
@@ -117,7 +122,7 @@ def test_tabular_optimizer_trial_params_no_trial_parameters(spectral_tabular_dat
         model=model, configs=configs, data=data
     )
     trial = optuna.trial.FixedTrial({})
-    with pytest.raises(ValueError, match="Trial parameters are not defined."):
+    with pytest.raises(InvalidInputError, match="Trial parameters are not defined."):
         optimizer._trial_params(trial)
 
 
@@ -129,5 +134,5 @@ def test_tabular_optimizer_scorer_no_scorer_defined(spectral_tabular_dataset):
     optimizer = TabularOptimizer.from_tabular_dataset_data(
         model=model, configs=configs, data=data
     )
-    with pytest.raises(ValueError, match="Scorer is not defined."):
+    with pytest.raises(InvalidInputError, match="Scorer is not defined."):
         optimizer.scorer()
diff --git a/tests/utils/test_utils_evaluators.py b/tests/utils/test_utils_evaluators.py
index a220f87..fdf8a7f 100644
--- a/tests/utils/test_utils_evaluators.py
+++ b/tests/utils/test_utils_evaluators.py
@@ -6,6 +6,7 @@
 from sklearn.model_selection import KFold, train_test_split
 from sklearn.svm import SVC
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.utils.evaluators import (
     cross_validation,
     hold_out_validation,
@@ -72,7 +73,7 @@ def test_hold_out_validation_with_incomplete_manual_validation_set(
     X, y = mock_sklearn_dataset
     X_train, X_val, y_train, _ = train_test_split(X, y, test_size=0.2, random_state=0)
     with pytest.raises(
-        ValueError,
+        InvalidInputError,
         match="To manually define validation set, both X_val and y_val must be specified.",
     ):
         hold_out_validation(
diff --git a/tests/utils/test_utils_general.py b/tests/utils/test_utils_general.py
index 84097ca..91ee637 100644
--- a/tests/utils/test_utils_general.py
+++ b/tests/utils/test_utils_general.py
@@ -6,6 +6,7 @@
 
 import pytest
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.utils.general import (
     dict_zip,
     ensure_dir,
@@ -72,7 +73,7 @@ def test_get_number_cpus():
     assert (
         get_number_cpus(multiprocessing.cpu_count() + 10) == multiprocessing.cpu_count()
     )
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         get_number_cpus(0)
 
 
@@ -83,7 +84,7 @@ def test_dict_zip():
     assert zipped == [("a", 1, 3), ("b", 2, 4)]
     dict1 = {"a": 1, "b": 2}
     dict2 = {"a": 3}
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         list(dict_zip(dict1, dict2))
     assert list(dict_zip()) == []
 
diff --git a/tests/utils/test_utils_plots.py b/tests/utils/test_utils_plots.py
index 512e4fa..d21000c 100644
--- a/tests/utils/test_utils_plots.py
+++ b/tests/utils/test_utils_plots.py
@@ -1,5 +1,6 @@
 import pytest
 
+from siapy.core.exceptions import InvalidInputError
 from siapy.datasets.schemas import (
     RegressionTarget,
     TabularDatasetData,
@@ -93,5 +94,5 @@ def test_display_signals():
     dataset.target = RegressionTarget.from_iterable(
         [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
     )
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         display_signals(dataset)
diff --git a/tests/utils/test_utils_validators.py b/tests/utils/test_utils_validators.py
index 09a23a4..5f9b1c6 100644
--- a/tests/utils/test_utils_validators.py
+++ b/tests/utils/test_utils_validators.py
@@ -3,6 +3,11 @@
 from PIL import Image as PILImage
 from sklearn.base import BaseEstimator
 
+from siapy.core.exceptions import (
+    InvalidInputError,
+    InvalidTypeError,
+    MethodNotImplementedError,
+)
 from siapy.utils.validators import (
     check_model_prediction_methods,
     validate_image_size,
@@ -36,7 +41,7 @@ def test_validate_image_to_numpy_3channels_with_numpy_array():
 
 def test_validate_image_to_numpy_3channels_with_invalid_input():
     mock_numpy_array = np.random.rand(100, 100, 4)
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         validate_image_to_numpy_3channels(mock_numpy_array)
 
 
@@ -60,7 +65,7 @@ def test_validate_image_to_numpy_with_numpy_array():
 
 
 def test_validate_image_to_numpy_with_invalid_input():
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         validate_image_to_numpy("invalid_input")
 
 
@@ -71,13 +76,13 @@ def test_validate_image_size():
     output_size_tuple = validate_image_size((100, 150))
     assert output_size_tuple == (100, 150)
 
-    with pytest.raises(TypeError):
+    with pytest.raises(InvalidTypeError):
         validate_image_size("invalid")
 
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         validate_image_size((100,))
 
-    with pytest.raises(ValueError):
+    with pytest.raises(InvalidInputError):
         validate_image_size((100, "150"))
 
 
@@ -105,7 +110,7 @@ def test_check_model_prediction_methods_valid():
 def test_check_model_prediction_methods_invalid():
     model = MockModelInvalid()
     with pytest.raises(
-        AttributeError,
-        match="The model must have methods: 'fit', 'predict', and 'score'.",
+        MethodNotImplementedError,
+        # match="The model must have methods: 'fit', 'predict', and 'score'.",
     ):
         check_model_prediction_methods(model)