Repository: vortico/flama (language: python)

Issue:
Support for domain-driven-design (DDD)
## Summary
This issue aims to implement a Domain-Driven Design (DDD) pattern to simplify the interface of resources within `Flama` applications, with specifical application on CRUD (Create, Read, Update, Delete) resources. DDD promotes a structured and domain-centric approach to software design, enhancing the clarity, maintainability, and scalability of our codebase.

## Motivation
Our current resource interfaces can be improved in structure to avoid code duplication when implementing them, or new ones. By implementing a DDD pattern, we can achieve a more organised and maintainable codebase, making it easier to onboard new developers and extend our application's functionality.

## Scope

- Implement the DDD pattern, and integrate it with `Flama` using components and modules.
- Refactor existing resource interfaces to adhere to the DDD pattern.
- Ensure backward compatibility with existing resource usage.


Discussion / hints:

Fix patch:
diff --git a/flama/__init__.py b/flama/__init__.py
index 31ab8970..d6aed1d7 100644
--- a/flama/__init__.py
+++ b/flama/__init__.py
@@ -4,6 +4,5 @@
 from flama.endpoints import *  # noqa
 from flama.injection.components import Component  # noqa
 from flama.modules import Module  # noqa
-from flama.pagination import *  # noqa
 from flama.routing import *  # noqa
 from flama.serialize import *  # noqa
diff --git a/flama/applications.py b/flama/applications.py
index 32a6d8d2..8e8218a2 100644
--- a/flama/applications.py
+++ b/flama/applications.py
@@ -2,6 +2,7 @@
 import typing as t
 
 from flama import asgi, http, injection, types, url, validation, websockets
+from flama.ddd.components import WorkerComponent
 from flama.events import Events
 from flama.middleware import MiddlewareStack
 from flama.models.modules import ModelsModule
@@ -14,6 +15,7 @@
 if t.TYPE_CHECKING:
     from flama.middleware import Middleware
     from flama.modules import Module
+    from flama.pagination.types import PaginationType
     from flama.routing import Mount, Route, WebSocketRoute
 
 __all__ = ["Flama"]
@@ -77,25 +79,23 @@ def __init__(
             }
         )
 
+        # Initialize Modules
+        default_modules = [
+            ResourcesModule(),
+            SchemaModule(title, version, description, schema=schema, docs=docs),
+            ModelsModule(),
+        ]
+        self.modules = Modules(app=self, modules={*default_modules, *(modules or [])})
+
         # Initialize router
-        self.app = self.router = Router(routes=routes, components=components, lifespan=lifespan)
+        default_components = [WorkerComponent(worker=default_modules[0].worker)]
+        self.app = self.router = Router(
+            routes=routes, components=[*default_components, *(components or [])], lifespan=lifespan
+        )
 
         # Build middleware stack
         self.middleware = MiddlewareStack(app=self.app, middleware=middleware or [], debug=debug)
 
-        # Initialize Modules
-        self.modules = Modules(
-            app=self,
-            modules={
-                *[
-                    ResourcesModule(),
-                    SchemaModule(title, version, description, schema=schema, docs=docs),
-                    ModelsModule(),
-                ],
-                *(modules or []),
-            },
-        )
-
         # Setup schema library
         self.schema.schema_library = schema_library
 
@@ -110,6 +110,9 @@ def __init__(
         # Reference to paginator from within app
         self.paginator = paginator
 
+        # Build router to propagate root application
+        self.router.build(self)
+
     def __getattr__(self, item: str) -> t.Any:
         """Retrieve a module by its name.
 
@@ -163,6 +166,7 @@ def add_route(
         name: t.Optional[str] = None,
         include_in_schema: bool = True,
         route: t.Optional["Route"] = None,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> "Route":
         """Register a new HTTP route or endpoint under given path.
@@ -173,6 +177,7 @@ def add_route(
         :param name: Endpoint or route name.
         :param include_in_schema: True if this route or endpoint should be declared as part of the API schema.
         :param route: HTTP route.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the route.
         """
         return self.router.add_route(
@@ -183,6 +188,7 @@ def add_route(
             include_in_schema=include_in_schema,
             route=route,
             root=self,
+            pagination=pagination,
             tags=tags,
         )
 
@@ -192,6 +198,7 @@ def route(
         methods: t.Optional[t.List[str]] = None,
         name: t.Optional[str] = None,
         include_in_schema: bool = True,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> t.Callable[[types.HTTPHandler], types.HTTPHandler]:
         """Decorator version for registering a new HTTP route in this router under given path.
@@ -200,11 +207,18 @@ def route(
         :param methods: List of valid HTTP methods (only applies for routes).
         :param name: Endpoint or route name.
         :param include_in_schema: True if this route or endpoint should be declared as part of the API schema.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the route.
         :return: Decorated route.
         """
         return self.router.route(
-            path, methods=methods, name=name, include_in_schema=include_in_schema, root=self, tags=tags
+            path,
+            methods=methods,
+            name=name,
+            include_in_schema=include_in_schema,
+            root=self,
+            pagination=pagination,
+            tags=tags,
         )
 
     def add_websocket_route(
@@ -213,6 +227,7 @@ def add_websocket_route(
         endpoint: t.Optional[types.WebSocketHandler] = None,
         name: t.Optional[str] = None,
         route: t.Optional["WebSocketRoute"] = None,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> "WebSocketRoute":
         """Register a new websocket route or endpoint under given path.
@@ -221,21 +236,29 @@ def add_websocket_route(
         :param endpoint: Websocket endpoint.
         :param name: Endpoint or route name.
         :param route: Websocket route.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the websocket route.
         """
-        return self.router.add_websocket_route(path, endpoint, name=name, route=route, root=self, tags=tags)
+        return self.router.add_websocket_route(
+            path, endpoint, name=name, route=route, root=self, pagination=pagination, tags=tags
+        )
 
     def websocket_route(
-        self, path: str, name: t.Optional[str] = None, tags: t.Optional[t.Dict[str, types.Tag]] = None
+        self,
+        path: str,
+        name: t.Optional[str] = None,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
+        tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> t.Callable[[types.WebSocketHandler], types.WebSocketHandler]:
         """Decorator version for registering a new websocket route in this router under given path.
 
         :param path: URL path.
         :param name: Websocket route name.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the websocket route.
         :return: Decorated route.
         """
-        return self.router.websocket_route(path, name=name, root=self, tags=tags)
+        return self.router.websocket_route(path, name=name, root=self, pagination=pagination, tags=tags)
 
     def mount(
         self,
diff --git a/flama/asgi.py b/flama/asgi.py
index 2a5ee35a..06710957 100644
--- a/flama/asgi.py
+++ b/flama/asgi.py
@@ -25,10 +25,10 @@ def resolve(self, scope: types.Scope) -> types.Method:
 
 class URLComponent(Component):
     def resolve(self, scope: types.Scope) -> types.URL:
-        host, port = scope.get("server", ["", ""])
+        host, port = scope.get("server", ["", None])
         scheme = scope.get("scheme", "")
 
-        if (scheme == "http" and port == 80) or (scheme == "https" and port == 443):
+        if (scheme == "http" and port in (80, None)) or (scheme == "https" and port in (443, None)):
             port = None
 
         return types.URL(f"{scheme}://{host}{f':{port}' if port else ''}{scope.get('raw_path', b'').decode()}")
diff --git a/flama/background.py b/flama/background.py
index d4857a9e..6ebea9ec 100644
--- a/flama/background.py
+++ b/flama/background.py
@@ -12,6 +12,15 @@
 
     t.ParamSpec = ParamSpec  # type: ignore
 
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
+
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
+
 __all__ = ["BackgroundTask", "BackgroundTasks", "Concurrency", "BackgroundThreadTask", "BackgroundProcessTask"]
 
 P = t.ParamSpec("P")  # type: ignore # PORT: Remove this comment when stop supporting 3.9
@@ -26,9 +35,9 @@ async def __call__(self, *args, **kwargs):
         await concurrency.run(self.target, *args, **kwargs)
 
 
-class Concurrency(enum.Enum):
-    thread = "thread"
-    process = "process"
+class Concurrency(enum.StrEnum):  # type: ignore # PORT: Remove this comment when stop supporting 3.10
+    thread = enum.auto()
+    process = enum.auto()
 
 
 class BackgroundTask(starlette.background.BackgroundTask):
@@ -42,7 +51,7 @@ def __init__(
         self.func = task_wrapper(func)
         self.args = args
         self.kwargs = kwargs
-        self.concurrency = Concurrency[concurrency] if isinstance(concurrency, str) else concurrency
+        self.concurrency = Concurrency[concurrency]
 
     async def __call__(self):
         if self.concurrency == Concurrency.process:
diff --git a/flama/concurrency.py b/flama/concurrency.py
index fb6f9a66..c66c002c 100644
--- a/flama/concurrency.py
+++ b/flama/concurrency.py
@@ -73,7 +73,7 @@ async def run_task_group(*tasks: t.Coroutine[t.Any, t.Any, t.Any]) -> t.List[asy
         await asyncio.wait(tasks_list)
         return tasks_list
 
-else:
+else:  # noqa
 
     async def run_task_group(*tasks: t.Coroutine[t.Any, t.Any, t.Any]) -> t.List[asyncio.Task]:
         """Run a group of tasks.
diff --git a/flama/config/config.py b/flama/config/config.py
index a1fe155c..25fc9aac 100644
--- a/flama/config/config.py
+++ b/flama/config/config.py
@@ -57,7 +57,7 @@ class Config:
     def __init__(
         self,
         config_file: t.Optional[t.Union[str, os.PathLike]] = None,
-        format: t.Union[str, types.FileFormat] = types.FileFormat.INI,
+        format: t.Union[str, types.FileFormat] = types.FileFormat.ini,
     ) -> None:
         """Tool for retrieving config parameters from a config file or environment variables.
 
diff --git a/flama/config/data_structures.py b/flama/config/data_structures.py
index cd3f5e7a..5f50d4c0 100644
--- a/flama/config/data_structures.py
+++ b/flama/config/data_structures.py
@@ -7,23 +7,23 @@
 
 
 class FileDict(t.Mapping[str, t.Any]):
-    """A dictionary that loads its data from a file. Supports JSON, TOML, YAML and INI files."""
+    """A dictionary that loads its data from a file. Supports json, toml, yaml and ini files."""
 
     _LOADERS: t.Dict[types.FileFormat, loaders.FileLoader] = {
-        types.FileFormat.JSON: loaders.JSONFileLoader(),
-        types.FileFormat.TOML: loaders.TOMLFileLoader(),
-        types.FileFormat.YAML: loaders.YAMLFileLoader(),
-        types.FileFormat.INI: loaders.ConfigFileLoader(),
+        types.FileFormat.json: loaders.JSONFileLoader(),
+        types.FileFormat.toml: loaders.TOMLFileLoader(),
+        types.FileFormat.yaml: loaders.YAMLFileLoader(),
+        types.FileFormat.ini: loaders.ConfigFileLoader(),
     }
 
     def __init__(self, config_file: t.Union[str, os.PathLike], format: t.Union[str, types.FileFormat]):
-        """A dictionary that loads its data from a file. Supports JSON, TOML, YAML and INI files.
+        """A dictionary that loads its data from a file. Supports json, toml, yaml and ini files.
 
         :param config_file: Config file path.
         :param format: Config file format.
         """
         try:
-            self._loader = self._LOADERS[types.FileFormat[format.upper()] if isinstance(format, str) else format]
+            self._loader = self._LOADERS[types.FileFormat[format]]
         except KeyError:
             raise exceptions.ConfigError("Wrong config file format")
 
diff --git a/flama/config/loaders.py b/flama/config/loaders.py
index a438e1df..2fdb9dae 100644
--- a/flama/config/loaders.py
+++ b/flama/config/loaders.py
@@ -14,7 +14,7 @@
         tomllib = tomli
     except ModuleNotFoundError:
         tomllib = None
-else:
+else:  # noqa
     import tomllib
 
 __all__ = ["FileLoader", "ConfigFileLoader", "JSONFileLoader", "YAMLFileLoader", "TOMLFileLoader"]
diff --git a/flama/config/types.py b/flama/config/types.py
index 0b807a04..6fa153db 100644
--- a/flama/config/types.py
+++ b/flama/config/types.py
@@ -1,12 +1,21 @@
 import enum
+import sys
 
 __all__ = ["FileFormat"]
 
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
 
-class FileFormat(enum.Enum):
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
+
+class FileFormat(enum.StrEnum):  # type: ignore # PORT: Remove this comment when stop supporting 3.10
     """Config file format."""
 
-    INI = enum.auto()
-    JSON = enum.auto()
-    YAML = enum.auto()
-    TOML = enum.auto()
+    ini = enum.auto()
+    json = enum.auto()
+    yaml = enum.auto()
+    toml = enum.auto()
diff --git a/flama/ddd/__init__.py b/flama/ddd/__init__.py
new file mode 100644
index 00000000..fa2b755f
--- /dev/null
+++ b/flama/ddd/__init__.py
@@ -0,0 +1,3 @@
+from flama.ddd.components import *  # noqa
+from flama.ddd.repositories import *  # noqa
+from flama.ddd.workers import *  # noqa
diff --git a/flama/ddd/components.py b/flama/ddd/components.py
new file mode 100644
index 00000000..ded3e76f
--- /dev/null
+++ b/flama/ddd/components.py
@@ -0,0 +1,23 @@
+import typing as t
+
+from flama import types
+from flama.ddd.workers import AbstractWorker
+from flama.injection import Component
+
+if t.TYPE_CHECKING:
+    from flama.injection import Parameter
+
+
+__all__ = ["WorkerComponent"]
+
+
+class WorkerComponent(Component):
+    def __init__(self, worker: AbstractWorker):
+        self.worker = worker
+
+    def can_handle_parameter(self, parameter: "Parameter") -> bool:
+        return parameter.type is self.worker.__class__
+
+    def resolve(self, scopes: types.Scope):
+        self.worker.app = scopes["app"]
+        return self.worker
diff --git a/flama/ddd/exceptions.py b/flama/ddd/exceptions.py
new file mode 100644
index 00000000..fa2583a0
--- /dev/null
+++ b/flama/ddd/exceptions.py
@@ -0,0 +1,13 @@
+__all__ = ["RepositoryException", "IntegrityError", "NotFoundError"]
+
+
+class RepositoryException(Exception):
+    ...
+
+
+class IntegrityError(RepositoryException):
+    ...
+
+
+class NotFoundError(RepositoryException):
+    ...
diff --git a/flama/ddd/repositories.py b/flama/ddd/repositories.py
new file mode 100644
index 00000000..783f7685
--- /dev/null
+++ b/flama/ddd/repositories.py
@@ -0,0 +1,130 @@
+import abc
+import typing as t
+
+from flama import types
+from flama.ddd import exceptions
+
+try:
+    import sqlalchemy
+    import sqlalchemy.exc
+except Exception:  # pragma: no cover
+    raise AssertionError("`sqlalchemy[asyncio]` must be installed to use crud resources") from None
+
+
+if t.TYPE_CHECKING:
+    from sqlalchemy.ext.asyncio import AsyncConnection
+
+
+__all__ = ["AbstractRepository", "SQLAlchemyRepository"]
+
+
+class AbstractRepository(abc.ABC):
+    ...
+
+
+class SQLAlchemyRepository(AbstractRepository):
+    _table: t.ClassVar[sqlalchemy.Table]
+
+    def __init__(self, connection: "AsyncConnection"):
+        self._connection = connection
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, SQLAlchemyRepository)
+            and self._table == other._table
+            and self._connection == other._connection
+        )
+
+    @property
+    def primary_key(self) -> sqlalchemy.Column:
+        """Returns the primary key of the model.
+
+        :return: sqlalchemy.Column: The primary key of the model.
+        :raises: exceptions.IntegrityError: If the model has a composed primary key.
+        """
+        model_pk_columns = list(sqlalchemy.inspect(self._table).primary_key.columns.values())
+
+        if len(model_pk_columns) != 1:
+            raise exceptions.IntegrityError("Composed primary keys are not supported")
+
+        return model_pk_columns[0]
+
+    async def create(self, data: t.Union[t.Dict[str, t.Any], types.Schema]) -> t.Optional[t.Tuple[t.Any, ...]]:
+        """Creates a new element in the repository.
+
+        :param data: The data to create the element.
+        :return: The primary key of the created element.
+        :raises: exceptions.IntegrityError: If the element already exists.
+        """
+        try:
+            result = await self._connection.execute(sqlalchemy.insert(self._table).values(**data))
+        except sqlalchemy.exc.IntegrityError as e:
+            raise exceptions.IntegrityError(str(e))
+        return tuple(result.inserted_primary_key) if result.inserted_primary_key else None
+
+    async def retrieve(self, id_: t.Any) -> types.Schema:
+        """Retrieves an element from the repository.
+
+        :param id_: The primary key of the element.
+        :return: The element.
+        :raises: exceptions.NotFoundError: If the element does not exist.
+        """
+        element = (
+            await self._connection.execute(
+                sqlalchemy.select(self._table).where(self._table.c[self.primary_key.name] == id_)
+            )
+        ).first()
+
+        if element is None:
+            raise exceptions.NotFoundError(str(id_))
+
+        return types.Schema(element._asdict())
+
+    async def update(self, id_: t.Any, data: types.Schema) -> types.Schema:
+        """Updates an element in the repository.
+
+        :param id_: The primary key of the element.
+        :param data: The data to update the element.
+        :return: The updated element.
+        :raises: exceptions.NotFoundError: If the element does not exist.
+        """
+        pk = self.primary_key
+        result = await self._connection.execute(
+            sqlalchemy.update(self._table).where(self._table.c[pk.name] == id_).values(**data)
+        )
+        if result.rowcount == 0:
+            raise exceptions.NotFoundError(id_)
+        return types.Schema({pk.name: id_, **data})
+
+    async def delete(self, id_: t.Any) -> None:
+        """Deletes an element from the repository.
+
+        :param id_: The primary key of the element.
+        :raises: exceptions.NotFoundError: If the element does not exist.
+        """
+        result = await self._connection.execute(
+            sqlalchemy.delete(self._table).where(self._table.c[self.primary_key.name] == id_)
+        )
+        if result.rowcount == 0:
+            raise exceptions.NotFoundError(id_)
+
+    async def list(self, *clauses, **filters) -> t.List[types.Schema]:
+        """Lists all the elements in the repository.
+
+        :param clauses: Clauses to filter the elements.
+        :param filters: Filters to filter the elements.
+        :return: The elements.
+        """
+        query = sqlalchemy.select(self._table)
+        where_clauses = tuple(clauses) + tuple(self._table.c[k] == v for k, v in filters.items())
+        if where_clauses:
+            query = query.where(sqlalchemy.and_(*where_clauses))
+        return [types.Schema(row._asdict()) async for row in await self._connection.stream(query)]
+
+    async def drop(self) -> int:
+        """Drops all the elements in the repository.
+
+        :return: The number of elements dropped.
+        """
+        result = await self._connection.execute(sqlalchemy.delete(self._table))
+        return result.rowcount
diff --git a/flama/ddd/types.py b/flama/ddd/types.py
new file mode 100644
index 00000000..8cb1ba26
--- /dev/null
+++ b/flama/ddd/types.py
@@ -0,0 +1,7 @@
+import typing as t
+
+from flama.ddd.repositories import AbstractRepository
+
+__all__ = ["Repositories"]
+
+Repositories = t.NewType("Repositories", t.Dict[str, t.Type[AbstractRepository]])
diff --git a/flama/ddd/workers.py b/flama/ddd/workers.py
new file mode 100644
index 00000000..f3c84df3
--- /dev/null
+++ b/flama/ddd/workers.py
@@ -0,0 +1,111 @@
+import abc
+import inspect
+import typing as t
+
+from sqlalchemy.ext.asyncio import AsyncTransaction
+
+from flama.ddd import types
+from flama.ddd.repositories import AbstractRepository, SQLAlchemyRepository
+
+if t.TYPE_CHECKING:
+    from sqlalchemy.ext.asyncio import AsyncConnection
+
+    from flama import Flama
+
+
+__all__ = ["WorkerType", "AbstractWorker", "SQLAlchemyWorker"]
+
+
+class WorkerType(abc.ABCMeta):
+    def __new__(mcs, name: str, bases: t.Tuple[type], namespace: t.Dict[str, t.Any]):
+        namespace["_repositories"] = types.Repositories(
+            {
+                k: v
+                for k, v in namespace.get("__annotations__", {}).items()
+                if inspect.isclass(v) and issubclass(v, AbstractRepository)
+            }
+        )
+
+        return super().__new__(mcs, name, bases, namespace)
+
+
+class AbstractWorker(abc.ABC):
+    _repositories: t.ClassVar[t.Dict[str, t.Type[AbstractRepository]]]
+
+    def __init__(self, app: t.Optional["Flama"] = None):
+        self._app = app
+
+    @property
+    def app(self) -> "Flama":
+        assert self._app, "Worker not initialized"
+        return self._app
+
+    @app.setter
+    def app(self, app: "Flama"):
+        self._app = app
+
+    @app.deleter
+    def app(self):
+        self._app = None
+
+    @abc.abstractmethod
+    async def __aenter__(self) -> "AbstractWorker":
+        ...
+
+    @abc.abstractmethod
+    async def __aexit__(self, exc_type, exc_val, exc_tb):
+        ...
+
+    @abc.abstractmethod
+    async def commit(self) -> None:
+        ...
+
+    @abc.abstractmethod
+    async def rollback(self) -> None:
+        ...
+
+
+class SQLAlchemyWorker(AbstractWorker, metaclass=WorkerType):
+    _repositories: t.ClassVar[t.Dict[str, t.Type[SQLAlchemyRepository]]]
+    _connection: "AsyncConnection"
+    _transaction: "AsyncTransaction"
+
+    @property
+    def connection(self) -> "AsyncConnection":
+        try:
+            return self._connection
+        except AttributeError:
+            raise AttributeError("Connection not initialized")
+
+    async def begin(self):
+        self._connection = self.app.sqlalchemy.engine.connect()
+        await self._connection.__aenter__()
+        self._transaction = self._connection.begin()
+        await self._transaction
+
+    async def close(self):
+        if hasattr(self, "_transaction"):
+            await self._transaction.__aexit__(None, None, None)
+            del self._transaction
+
+        if hasattr(self, "_connection"):
+            await self._connection.__aexit__(None, None, None)
+            del self._connection
+
+    async def __aenter__(self):
+        await self.begin()
+        for repository, repository_class in self._repositories.items():
+            setattr(self, repository, repository_class(self.connection))
+        return self
+
+    async def __aexit__(self, exc_type, exc_val, exc_tb):
+        await self.close()
+
+        for repository in self._repositories.keys():
+            delattr(self, repository)
+
+    async def commit(self):
+        await self.connection.commit()
+
+    async def rollback(self):
+        await self.connection.rollback()
diff --git a/flama/http.py b/flama/http.py
index 2740a623..f2ae0921 100644
--- a/flama/http.py
+++ b/flama/http.py
@@ -1,9 +1,11 @@
+import dataclasses
 import datetime
 import enum
 import html
 import inspect
 import json
 import os
+import sys
 import typing as t
 import uuid
 from pathlib import Path
@@ -17,6 +19,14 @@
 from flama import schemas, types
 from flama.exceptions import HTTPException, SerializationError
 
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
+
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
 __all__ = [
     "Method",
     "Request",
@@ -34,7 +44,9 @@
     "OpenAPIResponse",
 ]
 
-Method = enum.Enum("Method", ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"])
+Method = enum.StrEnum(  # type: ignore # PORT: Remove this comment when stop supporting 3.10
+    "Method", ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"]
+)
 Request = starlette.requests.Request
 
 
@@ -88,6 +100,8 @@ def default(self, obj):
             return obj.__name__
         if isinstance(obj, BaseException):
             return repr(obj)
+        if dataclasses.is_dataclass(obj):
+            return dataclasses.asdict(obj)
         return super().default(obj)
 
 
diff --git a/flama/models/resource.py b/flama/models/resource.py
index 819a0eb3..ad84f097 100644
--- a/flama/models/resource.py
+++ b/flama/models/resource.py
@@ -95,15 +95,11 @@ def __new__(mcs, name: str, bases: t.Tuple[type], namespace: t.Dict[str, t.Any])
         except AttributeError as e:
             raise ResourceAttributeError(str(e), name)
 
-        metadata_namespace = {
+        namespace.setdefault("_meta", data_structures.Metadata()).namespaces["model"] = {
             "component": component,
             "model": component.model,
             "model_type": component.get_model_type(),
         }
-        if "_meta" in namespace:
-            namespace["_meta"].namespaces["model"] = metadata_namespace
-        else:
-            namespace["_meta"] = data_structures.Metadata(namespaces={"model": metadata_namespace})
 
         return super().__new__(mcs, name, bases, namespace)
 
diff --git a/flama/pagination/mixins/__init__.py b/flama/pagination/mixins/__init__.py
index e69de29b..324913e7 100644
--- a/flama/pagination/mixins/__init__.py
+++ b/flama/pagination/mixins/__init__.py
@@ -0,0 +1,2 @@
+from flama.pagination.mixins.limit_offset import *  # noqa
+from flama.pagination.mixins.page_number import *  # noqa
diff --git a/flama/pagination/mixins/limit_offset.py b/flama/pagination/mixins/limit_offset.py
index 103f728b..f5e6e37a 100644
--- a/flama/pagination/mixins/limit_offset.py
+++ b/flama/pagination/mixins/limit_offset.py
@@ -92,7 +92,7 @@ def decorator(
 
 
 class LimitOffsetMixin:
-    def limit_offset(self, schema_name: str):
+    def _paginate_limit_offset(self, func: t.Callable) -> t.Callable:
         """
         Decorator for adding pagination behavior to a view. That decorator produces a view based on limit-offset and
         it adds three query parameters to control the pagination: limit, offset and count. Offset has a default value of
@@ -106,26 +106,23 @@ def limit_offset(self, schema_name: str):
         :param schema_name: Name used for output field.
         :return: Decorated view.
         """
+        schema = schemas.Schema.from_type(inspect.signature(func).return_annotation)
+        resource_schema = schema.unique_schema
+        schema_name = schema.name
 
-        def _inner(func: t.Callable) -> t.Callable:
-            resource_schema = schemas.Schema.from_type(inspect.signature(func).return_annotation).unique_schema
-            try:
-                schema_module, schema_class = schema_name.rsplit(".", 1)
-                paginated_schema_name = f"{schema_module}.LimitOffsetPaginated{schema_class}"
-            except ValueError:
-                paginated_schema_name = f"LimitOffsetPaginated{schema_name}"
-            schema = schemas.Schema.build(
-                paginated_schema_name,
-                schema=schemas.schemas.LimitOffset,
-                fields=[schemas.Field("data", resource_schema, multiple=True)],
-            ).unique_schema
-
-            decorator = LimitOffsetDecoratorFactory.decorate(func, schema)
-
-            self.schemas.update(  # type: ignore[attr-defined]
-                {schema_name: resource_schema, paginated_schema_name: schema}
-            )
+        try:
+            schema_module, schema_class = schema_name.rsplit(".", 1)
+            paginated_schema_name = f"{schema_module}.LimitOffsetPaginated{schema_class}"
+        except ValueError:  # noqa: safety net
+            paginated_schema_name = f"LimitOffsetPaginated{schema_name}"
+        schema = schemas.Schema.build(
+            paginated_schema_name,
+            schema=schemas.schemas.LimitOffset,
+            fields=[schemas.Field("data", resource_schema, multiple=True)],
+        ).unique_schema
+
+        decorator = LimitOffsetDecoratorFactory.decorate(func, schema)
 
-            return decorator
+        self.schemas.update({schema_name: resource_schema, paginated_schema_name: schema})  # type: ignore[attr-defined]
 
-        return _inner
+        return decorator
diff --git a/flama/pagination/mixins/page_number.py b/flama/pagination/mixins/page_number.py
index 693fe051..3e6a1438 100644
--- a/flama/pagination/mixins/page_number.py
+++ b/flama/pagination/mixins/page_number.py
@@ -99,7 +99,7 @@ def decorator(
 
 
 class PageNumberMixin:
-    def page_number(self, schema_name: str) -> t.Callable:
+    def _paginate_page_number(self, func: t.Callable) -> t.Callable:
         """
         Decorator for adding pagination behavior to a view. That decorator produces a view based on page numbering and
         it adds three query parameters to control the pagination: page, page_size and count. Page has a default value of
@@ -113,25 +113,23 @@ def page_number(self, schema_name: str) -> t.Callable:
         :param schema_name: Name used for output field.
         :return: Decorated view.
         """
+        schema = schemas.Schema.from_type(inspect.signature(func).return_annotation)
+        resource_schema = schema.unique_schema
+        schema_name = schema.name
 
-        def _inner(func: t.Callable) -> t.Callable:
-            resource_schema = schemas.Schema.from_type(inspect.signature(func).return_annotation).unique_schema
-            try:
-                schema_module, schema_class = schema_name.rsplit(".", 1)
-                paginated_schema_name = f"{schema_module}.PageNumberPaginated{schema_class}"
-            except ValueError:
-                paginated_schema_name = f"PageNumberPaginated{schema_name}"
-            schema = schemas.Schema.build(
-                paginated_schema_name,
-                schema=schemas.schemas.PageNumber,
-                fields=[schemas.Field("data", resource_schema, multiple=True)],
-            ).unique_schema
-
-            decorator = PageNumberDecoratorFactory.decorate(func, schema)
-
-            self.schemas.update(  # type: ignore[attr-defined]
-                {schema_name: resource_schema, paginated_schema_name: schema}
-            )
-            return decorator
+        try:
+            schema_module, schema_class = schema_name.rsplit(".", 1)
+            paginated_schema_name = f"{schema_module}.PageNumberPaginated{schema_class}"
+        except ValueError:  # noqa: safety net
+            paginated_schema_name = f"PageNumberPaginated{schema_name}"
+        schema = schemas.Schema.build(
+            paginated_schema_name,
+            schema=schemas.schemas.PageNumber,
+            fields=[schemas.Field("data", resource_schema, multiple=True)],
+        ).unique_schema
+
+        decorator = PageNumberDecoratorFactory.decorate(func, schema)
 
-        return _inner
+        self.schemas.update({schema_name: resource_schema, paginated_schema_name: schema})  # type: ignore[attr-defined]
+
+        return decorator
diff --git a/flama/pagination/paginator.py b/flama/pagination/paginator.py
index 97e4acb3..ce0f6b4e 100644
--- a/flama/pagination/paginator.py
+++ b/flama/pagination/paginator.py
@@ -1,12 +1,19 @@
-from flama.pagination.mixins.limit_offset import LimitOffsetMixin
-from flama.pagination.mixins.page_number import PageNumberMixin
+import typing as t
+
+from flama.pagination import mixins, types
 
 __all__ = ["paginator"]
 
 
-class Paginator(LimitOffsetMixin, PageNumberMixin):
+class Paginator(mixins.LimitOffsetMixin, mixins.PageNumberMixin):
     def __init__(self):
         self.schemas = {}
 
+    def paginate(self, pagination: t.Union[str, types.PaginationType], func: t.Callable) -> t.Callable:
+        return {
+            types.PaginationType.limit_offset: self._paginate_limit_offset,
+            types.PaginationType.page_number: self._paginate_page_number,
+        }[types.PaginationType[pagination]](func)
+
 
 paginator = Paginator()
diff --git a/flama/pagination/types.py b/flama/pagination/types.py
new file mode 100644
index 00000000..f1221ddd
--- /dev/null
+++ b/flama/pagination/types.py
@@ -0,0 +1,17 @@
+import enum
+import sys
+
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
+
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
+__all__ = ["PaginationType"]
+
+
+class PaginationType(enum.StrEnum):  # type: ignore # PORT: Remove this comment when stop supporting 3.10
+    page_number = enum.auto()
+    limit_offset = enum.auto()
diff --git a/flama/resources/crud.py b/flama/resources/crud.py
index f6b8c123..aa70f6b2 100644
--- a/flama/resources/crud.py
+++ b/flama/resources/crud.py
@@ -1,18 +1,11 @@
 import typing as t
 
-from flama import schemas
-
-try:
-    import sqlalchemy
-except Exception:  # pragma: no cover
-    raise AssertionError("`sqlalchemy[asyncio]` must be installed to use crud resources") from None
-
-import flama.schemas
-from flama import exceptions, http, types
-from flama.pagination import paginator
+from flama import exceptions, http, schemas, types
+from flama.ddd import exceptions as ddd_exceptions
 from flama.resources import data_structures
 from flama.resources.rest import RESTResourceType
 from flama.resources.routing import resource_method
+from flama.resources.workers import FlamaWorker
 
 __all__ = [
     "CreateMixin",
@@ -40,21 +33,18 @@ def _add_create(
         @resource_method("/", methods=["POST"], name=f"{name}-create")
         async def create(
             self,
-            scope: types.Scope,
-            element: types.Schema[rest_schemas.input.schema],  # type: ignore[name-defined,type-arg]
-        ) -> types.Schema[rest_schemas.output.schema]:  # type: ignore[name-defined,type-arg]
-            app = scope["app"]
-
+            worker: FlamaWorker,
+            element: types.Schema[rest_schemas.input.schema],
+        ) -> types.Schema[rest_schemas.output.schema]:
             if element.get(rest_model.primary_key.name) is None:
                 element.pop(rest_model.primary_key.name, None)
 
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = self.model.insert().values(**element)
-                result = await connection.execute(query)
+            async with worker:
+                result = await worker.repositories[self._meta.name].create(element)
 
             return http.APIResponse(  # type: ignore[return-value]
                 schema=rest_schemas.output.schema,
-                content={**element, **dict(zip([x.name for x in self.model.primary_key], result.inserted_primary_key))},
+                content={**element, **dict(zip([x.name for x in self.model.primary_key], result or []))},
                 status_code=201,
             )
 
@@ -86,20 +76,16 @@ def _add_retrieve(
     ) -> t.Dict[str, t.Any]:
         @resource_method("/{element_id}/", methods=["GET"], name=f"{name}-retrieve")
         async def retrieve(
-            self, scope: types.Scope, element_id: rest_model.primary_key.type  # type: ignore[name-defined]
-        ) -> types.Schema[rest_schemas.output.schema]:  # type: ignore[name-defined,type-arg]
-            app = scope["app"]
-
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = self.model.select().where(self.model.c[rest_model.primary_key.name] == element_id)
-                result = await connection.execute(query)
-                element = result.first()
-
-            if element is None:
+            self,
+            worker: FlamaWorker,
+            element_id: rest_model.primary_key.type,
+        ) -> types.Schema[rest_schemas.output.schema]:
+            try:
+                async with worker:
+                    return await worker.repositories[self._meta.name].retrieve(element_id)
+            except ddd_exceptions.NotFoundError:
                 raise exceptions.HTTPException(status_code=404)
 
-            return types.Schema(element._asdict())
-
         retrieve.__doc__ = f"""
             tags:
                 - {verbose_name}
@@ -132,34 +118,19 @@ def _add_update(
         @resource_method("/{element_id}/", methods=["PUT"], name=f"{name}-update")
         async def update(
             self,
-            scope: types.Scope,
-            element_id: rest_model.primary_key.type,  # type: ignore[name-defined]
-            element: types.Schema[rest_schemas.input.schema],  # type: ignore[name-defined,type-arg]
-        ) -> types.Schema[rest_schemas.output.schema]:  # type: ignore[name-defined,type-arg]
-            app = scope["app"]
-
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = self.model.select().where(
-                    self.model.select().where(self.model.c[rest_model.primary_key.name] == element_id).exists()
-                )
-                result = await connection.execute(query)
-                exists = result.scalar()
-
-            if not exists:
-                raise http.HTTPException(status_code=404)
-
+            worker: FlamaWorker,
+            element_id: rest_model.primary_key.type,
+            element: types.Schema[rest_schemas.input.schema],
+        ) -> types.Schema[rest_schemas.output.schema]:
             schema = schemas.Schema(rest_schemas.input.schema)
-            clean_element = {k: v for k, v in schema.dump(element).items() if k != rest_model.primary_key.name}
-
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = (
-                    self.model.update()
-                    .where(self.model.c[rest_model.primary_key.name] == element_id)
-                    .values(**clean_element)
-                )
-                await connection.execute(query)
-
-            return types.Schema({rest_model.primary_key.name: element_id, **clean_element})
+            clean_element = types.Schema[rest_schemas.input.schema](
+                {k: v for k, v in schema.dump(element).items() if k != rest_model.primary_key.name}
+            )
+            try:
+                async with worker:
+                    return await worker.repositories[self._meta.name].update(element_id, clean_element)
+            except ddd_exceptions.NotFoundError:
+                raise exceptions.HTTPException(status_code=404)
 
         update.__doc__ = f"""
             tags:
@@ -186,25 +157,13 @@ def _add_delete(
         cls, name: str, verbose_name: str, rest_model: data_structures.Model, **kwargs
     ) -> t.Dict[str, t.Any]:
         @resource_method("/{element_id}/", methods=["DELETE"], name=f"{name}-delete")
-        async def delete(
-            self, scope: types.Scope, element_id: rest_model.primary_key.type  # type: ignore[name-defined]
-        ):
-            app = scope["app"]
-
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = self.model.select().where(
-                    self.model.select().where(self.model.c[rest_model.primary_key.name] == element_id).exists()
-                )
-                result = await connection.execute(query)
-                exists = result.scalar()
-
-            if not exists:
+        async def delete(self, worker: FlamaWorker, element_id: rest_model.primary_key.type):
+            try:
+                async with worker:
+                    await worker.repositories[self._meta.name].delete(element_id)
+            except ddd_exceptions.NotFoundError:
                 raise exceptions.HTTPException(status_code=404)
 
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = self.model.delete().where(self.model.c[rest_model.primary_key.name] == element_id)
-                await connection.execute(query)
-
             return http.APIResponse(status_code=204)
 
         delete.__doc__ = f"""
@@ -231,25 +190,10 @@ class ListMixin:
     def _add_list(
         cls, name: str, verbose_name: str, rest_schemas: data_structures.Schemas, **kwargs
     ) -> t.Dict[str, t.Any]:
-        async def filter(self, app, *clauses, **filters) -> t.List[t.Dict]:
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = self.model.select()
-
-                where_clauses = tuple(clauses) + tuple(self.model.c[k] == v for k, v in filters.items())
-
-                if where_clauses:
-                    query = query.where(sqlalchemy.and_(*where_clauses))
-
-                return [row._asdict() async for row in await connection.stream(query)]
-
-        @resource_method("/", methods=["GET"], name=f"{name}-list")
-        @paginator.page_number(schema_name=rest_schemas.output.name)
-        async def list(
-            self, scope: types.Scope, **kwargs
-        ) -> types.Schema[rest_schemas.output.schema]:  # type: ignore[name-defined,type-arg]
-            app = scope["app"]
-
-            return await self._filter(app)  # type: ignore[no-any-return,return-value]
+        @resource_method("/", methods=["GET"], name=f"{name}-list", pagination="page_number")
+        async def list(self, worker: FlamaWorker, **kwargs) -> types.Schema[rest_schemas.output.schema]:
+            async with worker:
+                return await worker.repositories[self._meta.name].list()  # type: ignore[return-value]
 
         list.__doc__ = f"""
             tags:
@@ -264,24 +208,19 @@ async def list(
                         List collection items.
         """
 
-        return {"_list": list, "_filter": filter}
+        return {"_list": list}
 
 
 class DropMixin:
     @classmethod
     def _add_drop(cls, name: str, verbose_name: str, **kwargs) -> t.Dict[str, t.Any]:
         @resource_method("/", methods=["DELETE"], name=f"{name}-drop")
-        async def drop(
-            self, scope: types.Scope
-        ) -> types.Schema[flama.schemas.schemas.DropCollection]:  # type: ignore[type-arg]
-            app = scope["app"]
-
-            async with app.sqlalchemy.engine.begin() as connection:
-                query = self.model.delete()
-                result = await connection.execute(query)
+        async def drop(self, worker: FlamaWorker) -> types.Schema[schemas.schemas.DropCollection]:
+            async with worker:
+                result = await worker.repositories[self._meta.name].drop()
 
             return http.APIResponse(  # type: ignore[return-value]
-                schema=flama.schemas.schemas.DropCollection, content={"deleted": result.rowcount}, status_code=204
+                schema=schemas.schemas.DropCollection, content={"deleted": result}, status_code=204
             )
 
         drop.__doc__ = f"""
diff --git a/flama/resources/data_structures.py b/flama/resources/data_structures.py
index 99cf58f5..1b76f3c7 100644
--- a/flama/resources/data_structures.py
+++ b/flama/resources/data_structures.py
@@ -1,10 +1,10 @@
 import dataclasses
-import typing
+import typing as t
 
 try:
     from sqlalchemy import Table
 except Exception:  # pragma: no cover
-    Table = typing.Any
+    Table = t.Any
 
 __all__ = ["Model", "PrimaryKey", "Schema", "Metadata", "MethodMetadata"]
 
@@ -24,7 +24,7 @@ class Model:
 @dataclasses.dataclass
 class Schema:
     name: str
-    schema: typing.Any
+    schema: t.Any
 
 
 @dataclasses.dataclass
@@ -35,11 +35,11 @@ class Schemas:
 
 @dataclasses.dataclass
 class Metadata:
-    name: typing.Optional[str] = None
-    verbose_name: typing.Optional[str] = None
-    namespaces: typing.Dict[str, typing.Dict[str, typing.Any]] = dataclasses.field(default_factory=dict)
+    name: str = dataclasses.field(init=False)
+    verbose_name: str = dataclasses.field(init=False)
+    namespaces: t.Dict[str, t.Dict[str, t.Any]] = dataclasses.field(default_factory=dict)
 
-    def to_plain_dict(self) -> typing.Dict[str, typing.Any]:
+    def to_plain_dict(self) -> t.Dict[str, t.Any]:
         return {
             "name": self.name,
             "verbose_name": self.verbose_name,
@@ -50,6 +50,6 @@ def to_plain_dict(self) -> typing.Dict[str, typing.Any]:
 @dataclasses.dataclass
 class MethodMetadata:
     path: str
-    methods: typing.Set[str] = dataclasses.field(default_factory=lambda: {"GET"})
-    name: typing.Optional[str] = None
-    tags: typing.Dict[str, typing.Any] = dataclasses.field(default_factory=dict)
+    methods: t.Set[str] = dataclasses.field(default_factory=lambda: {"GET"})
+    name: t.Optional[str] = None
+    tags: t.Dict[str, t.Any] = dataclasses.field(default_factory=dict)
diff --git a/flama/resources/modules.py b/flama/resources/modules.py
index d817262a..af6a74c2 100644
--- a/flama/resources/modules.py
+++ b/flama/resources/modules.py
@@ -4,6 +4,7 @@
 from flama.modules import Module
 from flama.resources.resource import BaseResource
 from flama.resources.routing import ResourceRoute
+from flama.resources.workers import FlamaWorker
 
 if t.TYPE_CHECKING:
     from flama import types
@@ -14,6 +15,10 @@
 class ResourcesModule(Module):
     name = "resources"
 
+    def __init__(self):
+        super().__init__()
+        self.worker = FlamaWorker()
+
     def add_resource(
         self,
         path: str,
diff --git a/flama/resources/resource.py b/flama/resources/resource.py
index 75184313..91d3c2e2 100644
--- a/flama/resources/resource.py
+++ b/flama/resources/resource.py
@@ -10,6 +10,7 @@
 class BaseResource:
     name: str
     verbose_name: str
+    _meta: data_structures.Metadata
 
 
 class ResourceType(type):
@@ -31,11 +32,8 @@ def __new__(mcs, name: str, bases: t.Tuple[type], namespace: t.Dict[str, t.Any])
         except AttributeError as e:
             raise ResourceAttributeError(str(e), name)
 
-        if "_meta" in namespace:
-            namespace["_meta"].name = resource_name
-            namespace["_meta"].verbose_name = verbose_name
-        else:
-            namespace["_meta"] = data_structures.Metadata(name=resource_name, verbose_name=verbose_name)
+        namespace.setdefault("_meta", data_structures.Metadata()).name = resource_name
+        namespace["_meta"].verbose_name = verbose_name
 
         # Create methods and routes
         namespace.update(mcs._build_methods(namespace))
diff --git a/flama/resources/rest.py b/flama/resources/rest.py
index 0771eeaf..e9c05e58 100644
--- a/flama/resources/rest.py
+++ b/flama/resources/rest.py
@@ -2,6 +2,7 @@
 import typing as t
 import uuid
 
+from flama.ddd.repositories import SQLAlchemyRepository
 from flama.resources import data_structures
 from flama.resources.exceptions import ResourceAttributeError
 from flama.resources.resource import BaseResource, ResourceType
@@ -51,11 +52,12 @@ def __new__(mcs, name: str, bases: t.Tuple[type], namespace: t.Dict[str, t.Any])
         except AttributeError as e:
             raise ResourceAttributeError(str(e), name)
 
-        metadata_namespace = {"model": model, "schemas": resource_schemas}
-        if "_meta" in namespace:
-            namespace["_meta"].namespaces["rest"] = metadata_namespace
-        else:
-            namespace["_meta"] = data_structures.Metadata(namespaces={"rest": metadata_namespace})
+        namespace.setdefault("_meta", data_structures.Metadata()).namespaces.update(
+            {
+                "rest": {"model": model, "schemas": resource_schemas},
+                "ddd": {"repository": type(f"{name}Repository", (SQLAlchemyRepository,), {"_table": model.table})},
+            }
+        )
 
         return super().__new__(mcs, name, bases, namespace)
 
diff --git a/flama/resources/routing.py b/flama/resources/routing.py
index 7d770ae7..1df1780c 100644
--- a/flama/resources/routing.py
+++ b/flama/resources/routing.py
@@ -1,11 +1,13 @@
 import inspect
 import typing as t
 
+from flama.pagination import paginator
 from flama.resources import data_structures
 from flama.routing import Mount, Route
 
 if t.TYPE_CHECKING:
-    from flama import types
+    from flama import Flama, types
+    from flama.pagination.types import PaginationType
     from flama.resources import BaseResource
 
 __all__ = ["ResourceRoute", "resource_method"]
@@ -40,11 +42,28 @@ def __init__(
 
         super().__init__(path=path, routes=routes, name=self.resource._meta.name)  # type: ignore
 
+    def build(self, app: t.Optional["Flama"] = None) -> None:
+        """Build step for resource routes.
+
+        Add this resource's repository to Flama's worker.
+
+        :param app: Flama app.
+        """
+        from flama import Flama
+
+        super().build(app)
+        if (root := (self.app if isinstance(self.app, Flama) else app)) and "ddd" in self.resource._meta.namespaces:
+            root.resources.worker.add_repository(
+                self.resource._meta.name, self.resource._meta.namespaces["ddd"]["repository"]
+            )
+
 
 def resource_method(
     path: str,
     methods: t.Optional[t.Sequence[str]] = None,
     name: t.Optional[str] = None,
+    *,
+    pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
     tags: t.Optional[t.Dict[str, "types.Tag"]] = None,
 ) -> t.Callable:
     """Decorator for adding useful info needed for generating resource routes.
@@ -52,11 +71,14 @@ def resource_method(
     :param path: Route path.
     :param methods: HTTP methods available.
     :param name: Route name.
+    :param pagination: Apply a pagination technique.
     :param tags: Tags to add to the method.
     :return: Decorated method.
     """
 
-    def wrapper(func):
+    def wrapper(func: t.Callable) -> t.Callable:
+        func = paginator.paginate(pagination, func) if pagination is not None else func
+
         func._meta = data_structures.MethodMetadata(
             path=path, methods=set(methods) if methods is not None else {"GET"}, name=name, tags=tags or {}
         )
diff --git a/flama/resources/workers.py b/flama/resources/workers.py
new file mode 100644
index 00000000..040fb2fd
--- /dev/null
+++ b/flama/resources/workers.py
@@ -0,0 +1,30 @@
+import typing as t
+
+from flama.ddd import SQLAlchemyWorker
+
+if t.TYPE_CHECKING:
+    from flama import Flama
+    from flama.ddd.repositories import SQLAlchemyRepository
+
+
+class FlamaWorker(SQLAlchemyWorker):
+    def __init__(self, app: t.Optional["Flama"] = None):
+        super().__init__(app)
+        self._init_repositories: t.Optional[t.Dict[str, "SQLAlchemyRepository"]] = None
+
+    @property
+    def repositories(self) -> t.Dict[str, "SQLAlchemyRepository"]:
+        assert self._init_repositories, "Repositories not initialized"
+        return self._init_repositories
+
+    async def __aenter__(self):
+        await self.begin()
+        self._init_repositories = {r: cls(self.connection) for r, cls in self._repositories.items()}
+        return self
+
+    async def __aexit__(self, exc_type, exc_val, exc_tb):
+        await self.close()
+        del self._init_repositories
+
+    def add_repository(self, name: str, cls: t.Type["SQLAlchemyRepository"]) -> None:
+        self._repositories[name] = cls
diff --git a/flama/routing.py b/flama/routing.py
index 7e89b089..f77903e0 100644
--- a/flama/routing.py
+++ b/flama/routing.py
@@ -8,6 +8,7 @@
 from flama import concurrency, endpoints, exceptions, http, schemas, types, url, websockets
 from flama.injection import Component, Components
 from flama.lifespan import Lifespan
+from flama.pagination import paginator
 from flama.schemas.routing import RouteParametersMixin
 
 if sys.version_info < (3, 10):  # PORT: Remove when stop supporting 3.9 # pragma: no cover
@@ -15,15 +16,24 @@
 
     t.TypeGuard = TypeGuard  # type: ignore
 
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
+
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
 if t.TYPE_CHECKING:
     from flama.applications import Flama
+    from flama.pagination.types import PaginationType
 
 __all__ = ["Route", "WebSocketRoute", "Mount", "Router"]
 
 logger = logging.getLogger(__name__)
 
 
-class _EndpointType(enum.Enum):
+class _EndpointType(enum.StrEnum):  # type: ignore # PORT: Remove this comment when stop supporting 3.10
     http = enum.auto()
     websocket = enum.auto()
 
@@ -41,11 +51,16 @@ def __init__(
         self,
         handler: t.Union[t.Callable, t.Type[endpoints.HTTPEndpoint], t.Type[endpoints.WebSocketEndpoint]],
         endpoint_type: _EndpointType,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
     ):
         """Wraps a function or endpoint into ASGI application.
 
         :param handler: Function or endpoint.
+        :param endpoint_type: Endpoint type, http or websocket.
+        :param pagination: Apply a pagination technique.
         """
+        if pagination:
+            handler = paginator.paginate(pagination, handler)
 
         self.handler = handler
         functools.update_wrapper(self, handler)
@@ -292,6 +307,7 @@ def __init__(
         methods: t.Optional[t.Union[t.Set[str], t.Sequence[str]]] = None,
         name: t.Optional[str] = None,
         include_in_schema: bool = True,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> None:
         """A route definition of a http endpoint.
@@ -301,6 +317,7 @@ def __init__(
         :param methods: List of valid HTTP methods.
         :param name: Route name.
         :param include_in_schema: True if this route must be listed as part of the App schema.
+        :param pagination: Apply a pagination technique.
         :param tags: Route tags.
         """
         assert self.is_endpoint(endpoint) or (
@@ -319,7 +336,7 @@ def __init__(
 
         super().__init__(
             path,
-            EndpointWrapper(endpoint, EndpointWrapper.type.http),
+            EndpointWrapper(endpoint, EndpointWrapper.type.http, pagination=pagination),
             name=name,
             include_in_schema=include_in_schema,
             tags=tags,
@@ -381,6 +398,7 @@ def __init__(
         *,
         name: t.Optional[str] = None,
         include_in_schema: bool = True,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ):
         """A route definition of a websocket endpoint.
@@ -389,6 +407,7 @@ def __init__(
         :param endpoint: Websocket endpoint or function.
         :param name: Route name.
         :param include_in_schema: True if this route must be listed as part of the App schema.
+        :param pagination: Apply a pagination technique.
         :param tags: Route tags.
         """
 
@@ -400,7 +419,7 @@ def __init__(
 
         super().__init__(
             path,
-            EndpointWrapper(endpoint, EndpointWrapper.type.websocket),
+            EndpointWrapper(endpoint, EndpointWrapper.type.websocket, pagination=pagination),
             name=name,
             include_in_schema=include_in_schema,
             tags=tags,
@@ -480,9 +499,14 @@ def build(self, app: t.Optional["Flama"] = None) -> None:
 
         :param app: Flama app.
         """
-        if app:
+        from flama import Flama
+
+        if app and isinstance(self.app, Flama):
+            self.app.router.components = Components(self.app.router.components + app.components)
+
+        if root := (self.app if isinstance(self.app, Flama) else app):
             for route in self.routes:
-                route.build(app)
+                route.build(root)
 
     def match(self, scope: types.Scope) -> Match:
         """Check if this route matches with given scope.
@@ -510,6 +534,9 @@ def route_scope(self, scope: types.Scope) -> types.Scope:
         :param scope: ASGI scope.
         :return: Route scope.
         """
+        from flama import Flama
+
+        app = self.app if isinstance(self.app, Flama) else scope["app"]
         path = scope["path"]
         root_path = scope.get("root_path", "")
         matched_params = self.path.values(path)
@@ -517,6 +544,7 @@ def route_scope(self, scope: types.Scope) -> types.Scope:
         matched_path = path[: -len(remaining_path)]
         return types.Scope(
             {
+                "app": app,
                 "path_params": {**dict(scope.get("path_params", {})), **matched_params},
                 "endpoint": self.endpoint,
                 "root_path": root_path + matched_path,
@@ -575,7 +603,7 @@ def __init__(
         :param root: Flama application.
         """
         self.routes = [] if routes is None else list(routes)
-        self._components = Components(components if components else set())
+        self.components = Components(components if components else set())
         self.lifespan = Lifespan(lifespan)
 
         if root:
@@ -611,26 +639,12 @@ def build(self, app: "Flama") -> None:
         for route in self.routes:
             route.build(app)
 
-    @property
-    def components(self) -> Components:
-        return Components(
-            self._components
-            + Components(
-                [
-                    component
-                    for route in self.routes
-                    if hasattr(route, "app") and hasattr(route.app, "components")
-                    for component in getattr(route.app, "components", [])
-                ]
-            )
-        )
-
     def add_component(self, component: Component):
         """Register a new component.
 
         :param component: Component to register.
         """
-        self._components = Components(self._components + Components([component]))
+        self.components = Components(self.components + Components([component]))
 
     def add_route(
         self,
@@ -639,8 +653,10 @@ def add_route(
         methods: t.Optional[t.List[str]] = None,
         name: t.Optional[str] = None,
         include_in_schema: bool = True,
+        *,
         route: t.Optional[Route] = None,
         root: t.Optional["Flama"] = None,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> Route:
         """Register a new HTTP route in this router under given path.
@@ -652,12 +668,19 @@ def add_route(
         :param include_in_schema: True if this route or endpoint should be declared as part of the API schema.
         :param route: HTTP route.
         :param root: Flama application.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the route or endpoint.
         :return: Route.
         """
         if path is not None and endpoint is not None:
             route = Route(
-                path, endpoint=endpoint, methods=methods, name=name, include_in_schema=include_in_schema, tags=tags
+                path,
+                endpoint=endpoint,
+                methods=methods,
+                name=name,
+                include_in_schema=include_in_schema,
+                pagination=pagination,
+                tags=tags,
             )
 
         assert route is not None, "Either 'path' and 'endpoint' or 'route' variables are needed"
@@ -674,7 +697,9 @@ def route(
         methods: t.Optional[t.List[str]] = None,
         name: t.Optional[str] = None,
         include_in_schema: bool = True,
+        *,
         root: t.Optional["Flama"] = None,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> t.Callable[[types.HTTPHandler], types.HTTPHandler]:
         """Decorator version for registering a new HTTP route in this router under given path.
@@ -684,13 +709,21 @@ def route(
         :param name: Endpoint or route name.
         :param include_in_schema: True if this route or endpoint should be declared as part of the API schema.
         :param root: Flama application.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the endpoint.
         :return: Decorated route.
         """
 
         def decorator(func: types.HTTPHandler) -> types.HTTPHandler:
             self.add_route(
-                path, func, methods=methods, name=name, include_in_schema=include_in_schema, root=root, tags=tags
+                path,
+                func,
+                methods=methods,
+                name=name,
+                include_in_schema=include_in_schema,
+                root=root,
+                pagination=pagination,
+                tags=tags,
             )
             return func
 
@@ -701,8 +734,10 @@ def add_websocket_route(
         path: t.Optional[str] = None,
         endpoint: t.Optional[types.WebSocketHandler] = None,
         name: t.Optional[str] = None,
+        *,
         route: t.Optional[WebSocketRoute] = None,
         root: t.Optional["Flama"] = None,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> WebSocketRoute:
         """Register a new websocket route in this router under given path.
@@ -712,11 +747,12 @@ def add_websocket_route(
         :param name: Websocket route name.
         :param route: Specific route class.
         :param root: Flama application.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the websocket route.
         :return: Websocket route.
         """
         if path is not None and endpoint is not None:
-            route = WebSocketRoute(path, endpoint=endpoint, name=name, tags=tags)
+            route = WebSocketRoute(path, endpoint=endpoint, name=name, pagination=pagination, tags=tags)
 
         assert route is not None, "Either 'path' and 'endpoint' or 'route' variables are needed"
 
@@ -730,7 +766,9 @@ def websocket_route(
         self,
         path: str,
         name: t.Optional[str] = None,
+        *,
         root: t.Optional["Flama"] = None,
+        pagination: t.Optional[t.Union[str, "PaginationType"]] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
     ) -> t.Callable[[types.WebSocketHandler], types.WebSocketHandler]:
         """Decorator version for registering a new websocket route in this router under given path.
@@ -738,12 +776,13 @@ def websocket_route(
         :param path: URL path.
         :param name: Websocket route name.
         :param root: Flama application.
+        :param pagination: Apply a pagination technique.
         :param tags: Tags to add to the websocket route.
         :return: Decorated websocket route.
         """
 
         def decorator(func: types.WebSocketHandler) -> types.WebSocketHandler:
-            self.add_websocket_route(path, func, name=name, root=root, tags=tags)
+            self.add_websocket_route(path, func, name=name, root=root, pagination=pagination, tags=tags)
             return func
 
         return decorator
@@ -753,6 +792,7 @@ def mount(
         path: t.Optional[str] = None,
         app: t.Optional[types.App] = None,
         name: t.Optional[str] = None,
+        *,
         mount: t.Optional[Mount] = None,
         root: t.Optional["Flama"] = None,
         tags: t.Optional[t.Dict[str, types.Tag]] = None,
diff --git a/flama/schemas/_libs/marshmallow/adapter.py b/flama/schemas/_libs/marshmallow/adapter.py
index d1ee7a44..6d507005 100644
--- a/flama/schemas/_libs/marshmallow/adapter.py
+++ b/flama/schemas/_libs/marshmallow/adapter.py
@@ -52,7 +52,7 @@ def build_field(
                 **field_args,
             )
 
-        return MAPPING[type_](**field_args)  # type: ignore[arg-type]
+        return MAPPING[type_](**field_args)
 
     def build_schema(
         self,
@@ -67,16 +67,16 @@ def build_schema(
 
     def validate(self, schema: t.Union[t.Type[Schema], Schema], values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
         try:
-            return self._schema_instance(schema).load(values, unknown=marshmallow.EXCLUDE)  # type: ignore
+            return t.cast(t.Dict[str, t.Any], self._schema_instance(schema).load(values, unknown=marshmallow.EXCLUDE))
         except marshmallow.ValidationError as exc:
             raise SchemaValidationError(errors=exc.normalized_messages())
 
     def load(self, schema: t.Union[t.Type[Schema], Schema], value: t.Dict[str, t.Any]) -> Schema:
-        return self._schema_instance(schema).load(value)  # type: ignore
+        return t.cast(Schema, self._schema_instance(schema).load(value))
 
     def dump(self, schema: t.Union[t.Type[Schema], Schema], value: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
         try:
-            return self._schema_instance(schema).dump(value)  # type: ignore
+            return t.cast(t.Dict[str, t.Any], self._schema_instance(schema).dump(value))
         except Exception as exc:
             raise SchemaValidationError(errors=str(exc))
 
@@ -87,14 +87,12 @@ def name(self, schema: t.Union[Schema, t.Type[Schema]]) -> str:
     def to_json_schema(self, schema: t.Union[t.Type[Schema], t.Type[Field], Schema, Field]) -> JSONSchema:
         json_schema: t.Dict[str, t.Any]
         try:
-            plugin = MarshmallowPlugin(
-                schema_name_resolver=lambda x: resolve_schema_cls(x).__name__  # type: ignore[no-any-return]
-            )
+            plugin = MarshmallowPlugin(schema_name_resolver=lambda x: resolve_schema_cls(x).__name__)
             APISpec("", "", "3.1.0", [plugin])
-            converter: "OpenAPIConverter" = plugin.converter  # type: ignore[assignment]
+            converter: "OpenAPIConverter" = t.cast("OpenAPIConverter", plugin.converter)
 
             if (inspect.isclass(schema) and issubclass(schema, Field)) or isinstance(schema, Field):
-                json_schema = converter.field2property(schema)  # type: ignore[arg-type]
+                json_schema = converter.field2property(t.cast(marshmallow.fields.Field, schema))
             elif inspect.isclass(schema) and issubclass(schema, Schema):
                 json_schema = converter.schema2jsonschema(schema)
             elif isinstance(schema, Schema):
@@ -124,10 +122,10 @@ def _get_field_type(self, field: Field) -> t.Union[Schema, t.Type]:
             return field.schema
 
         if isinstance(field, marshmallow.fields.List):
-            return self._get_field_type(field.inner)  # type: ignore
+            return self._get_field_type(t.cast(marshmallow.fields.Field, field.inner))
 
         if isinstance(field, marshmallow.fields.Dict):
-            return self._get_field_type(field.value_field)  # type: ignore
+            return self._get_field_type(t.cast(marshmallow.fields.Field, field.value_field))
 
         try:
             return MAPPING_TYPES[field.__class__]
diff --git a/flama/schemas/_libs/typesystem/adapter.py b/flama/schemas/_libs/typesystem/adapter.py
index ff201bb7..1ba267fd 100644
--- a/flama/schemas/_libs/typesystem/adapter.py
+++ b/flama/schemas/_libs/typesystem/adapter.py
@@ -121,7 +121,10 @@ def _get_field_type(
             )
 
         if isinstance(field, typesystem.Object):
-            return {k: self._get_field_type(v) for k, v in field.properties.items()}
+            object_fields = {k: self._get_field_type(v) for k, v in field.properties.items()}
+            if isinstance(field.additional_properties, (typesystem.Field, typesystem.Reference)):
+                object_fields[""] = self._get_field_type(field.additional_properties)
+            return object_fields
 
         try:
             return MAPPING_TYPES[field.__class__]
diff --git a/flama/schemas/data_structures.py b/flama/schemas/data_structures.py
index 1533cf2b..1d4fa1f4 100644
--- a/flama/schemas/data_structures.py
+++ b/flama/schemas/data_structures.py
@@ -11,13 +11,21 @@
 
     t.TypeGuard = TypeGuard  # type: ignore
 
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
+
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
 __all__ = ["Field", "Schema", "Parameter", "Parameters"]
 
 
 UNKNOWN = t.TypeVar("UNKNOWN")
 
 
-class ParameterLocation(enum.Enum):
+class ParameterLocation(enum.StrEnum):  # type: ignore # PORT: Remove this comment when stop supporting 3.10
     query = enum.auto()
     path = enum.auto()
     body = enum.auto()
@@ -126,9 +134,34 @@ def is_schema(cls, obj: t.Any) -> bool:
     def name(self) -> str:
         return schemas.adapter.name(self.schema)
 
+    def _fix_ref(self, value: str, refs: t.Dict[str, str]) -> str:
+        try:
+            prefix, name = value.rsplit("/", 1)
+            return f"{prefix}/{refs[name]}"
+        except KeyError:
+            return value
+
+    def _replace_json_schema_refs(self, schema: types.JSONField, refs: t.Dict[str, str]) -> types.JSONField:
+        if isinstance(schema, dict):
+            return {
+                k: self._fix_ref(t.cast(str, v), refs) if k == "$ref" else self._replace_json_schema_refs(v, refs)
+                for k, v in schema.items()
+            }
+
+        if isinstance(schema, (list, tuple, set)):
+            return [self._replace_json_schema_refs(x, refs) for x in schema]
+
+        return schema
+
     @property
     def json_schema(self) -> types.JSONSchema:
-        return schemas.adapter.to_json_schema(self.schema)
+        return t.cast(
+            types.JSONSchema,
+            self._replace_json_schema_refs(
+                schemas.adapter.to_json_schema(self.schema),
+                {Schema(x).name.rsplit(".", 1)[1]: Schema(x).name for x in self.nested_schemas()},
+            ),
+        )
 
     @property
     def unique_schema(self) -> t.Any:
@@ -143,7 +176,7 @@ def nested_schemas(self, schema: t.Any = UNKNOWN) -> t.List[t.Any]:
             return self.nested_schemas(self)
 
         if schemas.adapter.is_schema(schema):
-            return [schema]
+            return [schemas.adapter.unique_schema(schema)]
 
         if isinstance(schema, (list, tuple, set)):
             return [x for field in schema for x in self.nested_schemas(field)]
diff --git a/flama/schemas/generator.py b/flama/schemas/generator.py
index 95e4afdd..a8762be3 100644
--- a/flama/schemas/generator.py
+++ b/flama/schemas/generator.py
@@ -207,7 +207,7 @@ def register(self, schema: schemas.Schema, name: t.Optional[str] = None) -> int:
 
         try:
             schema_name = name or s.name
-        except ValueError as e:
+        except ValueError as e:  # noqa: safety net
             raise ValueError("Cannot infer schema name.") from e
 
         schema_instance = s.unique_schema
@@ -382,37 +382,6 @@ def _build_endpoint_body(
             },
         )
 
-    def _build_endpoint_response(
-        self, endpoint: EndpointInfo, metadata: t.Dict[str, t.Any]
-    ) -> t.Tuple[t.Optional[openapi.Response], str]:
-        try:
-            response_code, main_response = list(metadata.get("responses", {}).items())[0]
-        except IndexError:
-            response_code, main_response = "200", {}
-            logger.warning(
-                'OpenAPI description not provided in docstring for main response in endpoint "%s"', endpoint.path
-            )
-
-        if endpoint.response_parameter.schema.schema:
-            if endpoint.response_parameter.schema.schema not in self.schemas:
-                self.schemas.register(schema=endpoint.response_parameter.schema.schema)
-
-            content = {
-                "application/json": openapi.MediaType(
-                    schema=self.schemas.get_openapi_ref(endpoint.response_parameter.schema.schema)
-                )
-            }
-        else:
-            content = None
-
-        return (
-            openapi.Response(
-                description=main_response.get("description", "Description not provided."),
-                content=content,
-            ),
-            str(response_code),
-        )
-
     def _build_endpoint_default_response(self, metadata: t.Dict[str, t.Any]) -> openapi.Response:
         return openapi.Response(
             description=metadata.get("responses", {}).get("default", {}).get("description", "Unexpected error."),
diff --git a/flama/serialize/data_structures.py b/flama/serialize/data_structures.py
index 4dce6095..6dbbb8ff 100644
--- a/flama/serialize/data_structures.py
+++ b/flama/serialize/data_structures.py
@@ -8,6 +8,7 @@
 import logging
 import os
 import shutil
+import sys
 import tarfile
 import tempfile
 import typing as t
@@ -20,15 +21,32 @@
 from flama.serialize.exceptions import FrameworkVersionWarning
 from flama.serialize.types import Framework
 
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
+
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
+
 __all__ = ["ModelArtifact", "Compression"]
 
 logger = logging.getLogger(__name__)
 
 
-class Compression(enum.Enum):
-    fast = "gz"
-    standard = "bz2"
-    high = "xz"
+class Compression(enum.StrEnum):  # type: ignore # PORT: Remove this comment when stop supporting 3.10
+    fast = enum.auto()
+    standard = enum.auto()
+    high = enum.auto()
+
+    @property
+    def format(self) -> str:
+        return {
+            Compression.fast: "gz",
+            Compression.standard: "bz2",
+            Compression.high: "xz",
+        }[self]
 
 
 class FrameworkSerializers:
@@ -40,7 +58,7 @@ def serializer(cls, framework: t.Union[str, Framework]) -> Serializer:
                 Framework.sklearn: ("sklearn", "SKLearnSerializer"),
                 Framework.keras: ("tensorflow", "TensorFlowSerializer"),
                 Framework.tensorflow: ("tensorflow", "TensorFlowSerializer"),
-            }[Framework[framework] if isinstance(framework, str) else framework]
+            }[Framework[framework]]
         except KeyError:  # pragma: no cover
             raise ValueError("Wrong framework")
 
@@ -55,7 +73,7 @@ def from_model(cls, model: t.Any) -> Serializer:
 
         try:
             inspect_objs += model.__class__.__mro__
-        except AttributeError:
+        except AttributeError:  # noqa: safety net
             ...
 
         for obj in inspect_objs:
@@ -259,7 +277,7 @@ def from_dict(cls, data: t.Dict[str, t.Any], **kwargs) -> "ModelArtifact":
         except KeyError:  # pragma: no cover
             raise ValueError("Wrong data")
 
-        if serializer.version() != metadata.framework.version:
+        if serializer.version() != metadata.framework.version:  # noqa
             warnings.warn(
                 f"Model was built using {metadata.framework.lib.value} '{metadata.framework.version}' but detected "
                 f"version '{serializer.version()}' installed. This may cause unexpected behavior.",
@@ -306,8 +324,7 @@ def dump(
         :param kwargs: Keyword arguments passed to library dump method.
         """
         logger.info("Dump model '%s'", path)
-        compression = Compression[compression] if isinstance(compression, str) else compression
-        with tarfile.open(path, f"w:{compression.value}") as tar:
+        with tarfile.open(path, f"w:{Compression[compression].format}") as tar:
             if self.artifacts:
                 for name, path in self.artifacts.items():
                     tar.add(path, f"artifacts/{name}")
diff --git a/flama/serialize/exceptions.py b/flama/serialize/exceptions.py
index e16411ce..aed00404 100644
--- a/flama/serialize/exceptions.py
+++ b/flama/serialize/exceptions.py
@@ -1,3 +1,12 @@
+__all__ = ["FrameworkNotInstalled", "FrameworkVersionWarning"]
+
+
+class FrameworkNotInstalled(Exception):
+    """Cannot find an installed version of the framework."""
+
+    ...
+
+
 class FrameworkVersionWarning(Warning):
     """Warning for when a framework version does not match."""
 
diff --git a/flama/serialize/serializers/pytorch.py b/flama/serialize/serializers/pytorch.py
index 5596abfa..1aa3597b 100644
--- a/flama/serialize/serializers/pytorch.py
+++ b/flama/serialize/serializers/pytorch.py
@@ -3,8 +3,8 @@
 import io
 import typing as t
 
+from flama.serialize import exceptions, types
 from flama.serialize.base import Serializer
-from flama.serialize.types import Framework
 
 try:
     import torch
@@ -13,17 +13,21 @@
 
 
 class PyTorchSerializer(Serializer):
-    lib = Framework.torch
+    lib = types.Framework.torch
 
     def dump(self, obj: t.Any, **kwargs) -> bytes:
-        assert torch is not None, "`pytorch` must be installed to use PyTorchSerializer."
+        if torch is None:  # noqa
+            raise exceptions.FrameworkNotInstalled("pytorch")
+
         buffer = io.BytesIO()
         torch.jit.save(torch.jit.script(obj), buffer, **kwargs)
         buffer.seek(0)
         return codecs.encode(buffer.read(), "base64")
 
     def load(self, model: bytes, **kwargs) -> t.Any:
-        assert torch is not None, "`pytorch` must be installed to use PyTorchSerializer."
+        if torch is None:  # noqa
+            raise exceptions.FrameworkNotInstalled("pytorch")
+
         return torch.jit.load(io.BytesIO(codecs.decode(model, "base64")), **kwargs)
 
     def info(self, model: t.Any) -> t.Dict[str, t.Any]:
@@ -34,4 +38,7 @@ def info(self, model: t.Any) -> t.Dict[str, t.Any]:
         }
 
     def version(self) -> str:
-        return importlib.metadata.version("torch")
+        try:
+            return importlib.metadata.version("torch")
+        except Exception:  # noqa
+            raise exceptions.FrameworkNotInstalled("pytorch")
diff --git a/flama/serialize/serializers/sklearn.py b/flama/serialize/serializers/sklearn.py
index 03a8dab3..7639720f 100644
--- a/flama/serialize/serializers/sklearn.py
+++ b/flama/serialize/serializers/sklearn.py
@@ -6,15 +6,17 @@
 import typing as t
 import warnings
 
-from flama import types
+from flama.serialize import exceptions, types
 from flama.serialize.base import Serializer
-from flama.serialize.types import Framework
+
+if t.TYPE_CHECKING:
+    from flama.types import JSONField, JSONSchema
 
 logger = logging.getLogger(__name__)
 
 
 class SKLearnSerializer(Serializer):
-    lib = Framework.sklearn
+    lib = types.Framework.sklearn
 
     def dump(self, obj: t.Any, **kwargs) -> bytes:
         return codecs.encode(pickle.dumps(obj), "base64")
@@ -26,7 +28,7 @@ def load(self, model: bytes, **kwargs) -> t.Any:
 
         return model
 
-    def _info(self, data) -> types.JSONField:
+    def _info(self, data) -> "JSONField":
         if isinstance(data, (int, bool, str)):
             return data
 
@@ -41,7 +43,7 @@ def _info(self, data) -> types.JSONField:
 
         return None
 
-    def info(self, model: t.Any) -> t.Optional[types.JSONSchema]:
+    def info(self, model: t.Any) -> t.Optional["JSONSchema"]:
         try:
             return self._info(model.get_params())  # type: ignore
         except:  # noqa
@@ -49,4 +51,7 @@ def info(self, model: t.Any) -> t.Optional[types.JSONSchema]:
             return None
 
     def version(self) -> str:
-        return importlib.metadata.version("scikit-learn")
+        try:
+            return importlib.metadata.version("scikit-learn")
+        except Exception:  # noqa
+            raise exceptions.FrameworkNotInstalled("scikit-learn")
diff --git a/flama/serialize/serializers/tensorflow.py b/flama/serialize/serializers/tensorflow.py
index 10788552..2315ce4c 100644
--- a/flama/serialize/serializers/tensorflow.py
+++ b/flama/serialize/serializers/tensorflow.py
@@ -6,8 +6,8 @@
 import typing as t
 from tempfile import TemporaryDirectory
 
+from flama.serialize import exceptions, types
 from flama.serialize.base import Serializer
-from flama.serialize.types import Framework
 
 try:
     import tensorflow as tf
@@ -16,10 +16,12 @@
 
 
 class TensorFlowSerializer(Serializer):
-    lib = Framework.tensorflow
+    lib = types.Framework.tensorflow
 
     def dump(self, obj: t.Any, **kwargs) -> bytes:
-        assert tf is not None, "`tensorflow` must be installed to use TensorFlowSerializer."
+        if tf is None:  # noqa
+            raise exceptions.FrameworkNotInstalled("tensorflow")
+
         buffer = io.BytesIO()
         with TemporaryDirectory() as saved_model_dir, tarfile.open(fileobj=buffer, mode="w") as model_tar:
             tf.keras.models.save_model(obj, saved_model_dir)  # type: ignore
@@ -28,7 +30,9 @@ def dump(self, obj: t.Any, **kwargs) -> bytes:
         return codecs.encode(buffer.read(), "base64")
 
     def load(self, model: bytes, **kwargs) -> t.Any:
-        assert tf is not None, "`tensorflow` must be installed to use TensorFlowSerializer."
+        if tf is None:  # noqa
+            raise exceptions.FrameworkNotInstalled("tensorflow")
+
         with TemporaryDirectory() as saved_model_dir, tarfile.open(
             fileobj=io.BytesIO(codecs.decode(model, "base64")), mode="r:"
         ) as model_tar:
@@ -39,9 +43,11 @@ def info(self, model: t.Any) -> t.Dict[str, t.Any]:
         model_info: t.Dict[str, t.Any] = json.loads(model.to_json())
         return model_info
 
-    def version(self) -> str:  # type: ignore[return]
+    def version(self) -> str:
         for lib in ("tensorflow", "tensorflow-cpu", "tensorflow-gpu", "keras"):
             try:
                 return importlib.metadata.version(lib)
             except Exception:
                 pass
+
+        raise exceptions.FrameworkNotInstalled("tensorflow")  # noqa
diff --git a/flama/serialize/types.py b/flama/serialize/types.py
index d7f17c11..8eedebac 100644
--- a/flama/serialize/types.py
+++ b/flama/serialize/types.py
@@ -1,10 +1,19 @@
 import enum
+import sys
 
+if sys.version_info < (3, 11):  # PORT: Remove when stop supporting 3.10 # pragma: no cover
 
-class Framework(enum.Enum):
+    class StrEnum(str, enum.Enum):
+        def _generate_next_value_(name, start, count, last_values):
+            return name.lower()
+
+    enum.StrEnum = StrEnum  # type: ignore
+
+
+class Framework(enum.StrEnum):  # type: ignore # PORT: Replace with enum.StrEnum when stop supporting 3.10
     """ML formats available for Flama serialization."""
 
-    sklearn = "sklearn"
-    tensorflow = "tensorflow"
-    torch = "torch"
-    keras = "keras"
+    sklearn = enum.auto()
+    tensorflow = enum.auto()
+    torch = enum.auto()
+    keras = enum.auto()
diff --git a/poetry.lock b/poetry.lock
index 56ad1b98..7883bfa0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,22 +1,20 @@
-# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
 
 [[package]]
 name = "absl-py"
-version = "1.4.0"
+version = "2.0.0"
 description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py."
-category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 files = [
-    {file = "absl-py-1.4.0.tar.gz", hash = "sha256:d2c244d01048ba476e7c080bd2c6df5e141d211de80223460d5b3b8a2a58433d"},
-    {file = "absl_py-1.4.0-py3-none-any.whl", hash = "sha256:0d3fe606adfa4f7db64792dd4c7aee4ee0c38ab75dfd353b7a83ed3e957fcb47"},
+    {file = "absl-py-2.0.0.tar.gz", hash = "sha256:d9690211c5fcfefcdd1a45470ac2b5c5acd45241c3af71eed96bc5441746c0d5"},
+    {file = "absl_py-2.0.0-py3-none-any.whl", hash = "sha256:9a28abb62774ae4e8edbe2dd4c49ffcd45a6a848952a5eccc6a49f3f0fc1e2f3"},
 ]
 
 [[package]]
 name = "aiohttp"
 version = "3.8.5"
 description = "Async http client/server framework (asyncio)"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -125,7 +123,6 @@ speedups = ["Brotli", "aiodns", "cchardet"]
 name = "aiosignal"
 version = "1.3.1"
 description = "aiosignal: a list of registered asynchronous callbacks"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -140,7 +137,6 @@ frozenlist = ">=1.1.0"
 name = "aiosqlite"
 version = "0.19.0"
 description = "asyncio bridge to the standard sqlite3 module"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -156,7 +152,6 @@ docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"]
 name = "annotated-types"
 version = "0.5.0"
 description = "Reusable constraint types to use with typing.Annotated"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -171,7 +166,6 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
 name = "anyio"
 version = "4.0.0"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -193,7 +187,6 @@ trio = ["trio (>=0.22)"]
 name = "apispec"
 version = "6.3.0"
 description = "A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification)."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -217,7 +210,6 @@ yaml = ["PyYAML (>=3.10)"]
 name = "appnope"
 version = "0.1.3"
 description = "Disable App Nap on macOS >= 10.9"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -229,7 +221,6 @@ files = [
 name = "asttokens"
 version = "2.4.0"
 description = "Annotate AST trees with source code positions"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -247,7 +238,6 @@ test = ["astroid", "pytest"]
 name = "astunparse"
 version = "1.6.3"
 description = "An AST unparser for Python"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -263,7 +253,6 @@ wheel = ">=0.23.0,<1.0"
 name = "async-timeout"
 version = "4.0.3"
 description = "Timeout context manager for asyncio programs"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -275,7 +264,6 @@ files = [
 name = "attrs"
 version = "23.1.0"
 description = "Classes Without Boilerplate"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -294,7 +282,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
 name = "backcall"
 version = "0.2.0"
 description = "Specifications for callback functions passed in to an API"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -306,7 +293,6 @@ files = [
 name = "black"
 version = "22.12.0"
 description = "The uncompromising code formatter."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -343,7 +329,6 @@ uvloop = ["uvloop (>=0.15.2)"]
 name = "cachetools"
 version = "5.3.1"
 description = "Extensible memoizing collections and decorators"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -355,7 +340,6 @@ files = [
 name = "certifi"
 version = "2023.7.22"
 description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -367,7 +351,6 @@ files = [
 name = "cffi"
 version = "1.15.1"
 description = "Foreign Function Interface for Python calling C code."
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -444,7 +427,6 @@ pycparser = "*"
 name = "cfgv"
 version = "3.4.0"
 description = "Validate configuration and produce human readable error messages."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -456,7 +438,6 @@ files = [
 name = "charset-normalizer"
 version = "3.2.0"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "dev"
 optional = false
 python-versions = ">=3.7.0"
 files = [
@@ -541,7 +522,6 @@ files = [
 name = "click"
 version = "8.1.7"
 description = "Composable command line interface toolkit"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -556,7 +536,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
 name = "cmake"
 version = "3.27.5"
 description = "CMake is an open-source, cross-platform family of tools designed to build, test and package software"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -586,7 +565,6 @@ test = ["coverage (>=4.2)", "flake8 (>=3.0.4)", "path.py (>=11.5.0)", "pytest (>
 name = "colorama"
 version = "0.4.6"
 description = "Cross-platform colored terminal text."
-category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 files = [
@@ -598,7 +576,6 @@ files = [
 name = "coverage"
 version = "7.3.1"
 description = "Code coverage measurement for Python"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -666,7 +643,6 @@ toml = ["tomli"]
 name = "decorator"
 version = "5.1.1"
 description = "Decorators for Humans"
-category = "dev"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -678,7 +654,6 @@ files = [
 name = "distlib"
 version = "0.3.7"
 description = "Distribution utilities"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -690,7 +665,6 @@ files = [
 name = "exceptiongroup"
 version = "1.1.3"
 description = "Backport of PEP 654 (exception groups)"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -705,7 +679,6 @@ test = ["pytest (>=6)"]
 name = "execnet"
 version = "2.0.2"
 description = "execnet: rapid multi-Python deployment"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -720,7 +693,6 @@ testing = ["hatch", "pre-commit", "pytest", "tox"]
 name = "executing"
 version = "1.2.0"
 description = "Get the currently executing AST node of a frame, and other information"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -735,7 +707,6 @@ tests = ["asttokens", "littleutils", "pytest", "rich"]
 name = "faker"
 version = "8.16.0"
 description = "Faker is a Python package that generates fake data for you."
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -751,7 +722,6 @@ text-unidecode = "1.3"
 name = "filelock"
 version = "3.12.4"
 description = "A platform independent file lock."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -768,7 +738,6 @@ typing = ["typing-extensions (>=4.7.1)"]
 name = "flatbuffers"
 version = "23.5.26"
 description = "The FlatBuffers serialization format for Python"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -780,7 +749,6 @@ files = [
 name = "frozenlist"
 version = "1.4.0"
 description = "A list-like structure which implements collections.abc.MutableSequence"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -851,7 +819,6 @@ files = [
 name = "gast"
 version = "0.4.0"
 description = "Python AST that abstracts the underlying Python version"
-category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -859,99 +826,98 @@ files = [
     {file = "gast-0.4.0.tar.gz", hash = "sha256:40feb7b8b8434785585ab224d1568b857edb18297e5a3047f1ba012bc83b42c1"},
 ]
 
+[[package]]
+name = "gast"
+version = "0.5.4"
+description = "Python AST that abstracts the underlying Python version"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+    {file = "gast-0.5.4-py3-none-any.whl", hash = "sha256:6fc4fa5fa10b72fb8aab4ae58bcb023058386e67b6fa2e3e34cec5c769360316"},
+    {file = "gast-0.5.4.tar.gz", hash = "sha256:9c270fe5f4b130969b54174de7db4e764b09b4f7f67ccfc32480e29f78348d97"},
+]
+
 [[package]]
 name = "gevent"
-version = "22.10.2"
+version = "23.9.1"
 description = "Coroutine-based network library"
-category = "dev"
-optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5"
-files = [
-    {file = "gevent-22.10.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:97cd42382421779f5d82ec5007199e8a84aa288114975429e4fd0a98f2290f10"},
-    {file = "gevent-22.10.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1e1286a76f15b5e15f1e898731d50529e249529095a032453f2c101af3fde71c"},
-    {file = "gevent-22.10.2-cp27-cp27m-win32.whl", hash = "sha256:59b47e81b399d49a5622f0f503c59f1ce57b7705306ea0196818951dfc2f36c8"},
-    {file = "gevent-22.10.2-cp27-cp27m-win_amd64.whl", hash = "sha256:1d543c9407a1e4bca11a8932916988cfb16de00366de5bf7bc9e7a3f61e60b18"},
-    {file = "gevent-22.10.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4e2f008c82dc54ec94f4de12ca6feea60e419babb48ec145456907ae61625aa4"},
-    {file = "gevent-22.10.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:990d7069f14dc40674e0d5cb43c68fd3bad8337048613b9bb94a0c4180ffc176"},
-    {file = "gevent-22.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f23d0997149a816a2a9045af29c66f67f405a221745b34cefeac5769ed451db8"},
-    {file = "gevent-22.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b43d500d7d3c0e03070dee813335bb5315215aa1cf6a04c61093dfdd718640b3"},
-    {file = "gevent-22.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b68f4c9e20e47ad49fe797f37f91d5bbeace8765ce2707f979a8d4ec197e4d"},
-    {file = "gevent-22.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1f001cac0ba8da76abfeb392a3057f81fab3d67cc916c7df8ea977a44a2cc989"},
-    {file = "gevent-22.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:3b7eae8a0653ba95a224faaddf629a913ace408edb67384d3117acf42d7dcf89"},
-    {file = "gevent-22.10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8f2477e7b0a903a01485c55bacf2089110e5f767014967ba4b287ff390ae2638"},
-    {file = "gevent-22.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddaa3e310a8f1a45b5c42cf50b54c31003a3028e7d4e085059090ea0e7a5fddd"},
-    {file = "gevent-22.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98bc510e80f45486ef5b806a1c305e0e89f0430688c14984b0dbdec03331f48b"},
-    {file = "gevent-22.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877abdb3a669576b1d51ce6a49b7260b2a96f6b2424eb93287e779a3219d20ba"},
-    {file = "gevent-22.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21ad79cca234cdbfa249e727500b0ddcbc7adfff6614a96e6eaa49faca3e4f2"},
-    {file = "gevent-22.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e955238f59b2947631c9782a713280dd75884e40e455313b5b6bbc20b92ff73"},
-    {file = "gevent-22.10.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:5aa99e4882a9e909b4756ee799c6fa0f79eb0542779fad4cc60efa23ec1b2aa8"},
-    {file = "gevent-22.10.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d82081656a5b9a94d37c718c8646c757e1617e389cdc533ea5e6a6f0b8b78545"},
-    {file = "gevent-22.10.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f4bfd74c178351a4a05c5c7df6f8a0a279ff6f392b57608ce0e83c768207f9"},
-    {file = "gevent-22.10.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ff3796692dff50fec2f381b9152438b221335f557c4f9b811f7ded51b7a25a1"},
-    {file = "gevent-22.10.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f01c9adbcb605364694b11dcd0542ec468a29ac7aba2fb5665dc6caf17ba4d7e"},
-    {file = "gevent-22.10.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:9d85574eb729f981fea9a78998725a06292d90a3ed50ddca74530c3148c0be41"},
-    {file = "gevent-22.10.2-cp36-cp36m-win32.whl", hash = "sha256:8c192d2073e558e241f0b592c1e2b34127a4481a5be240cad4796533b88b1a98"},
-    {file = "gevent-22.10.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a2237451c721a0f874ef89dbb4af4fdc172b76a964befaa69deb15b8fff10f49"},
-    {file = "gevent-22.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:53ee7f170ed42c7561fe8aff5d381dc9a4124694e70580d0c02fba6aafc0ea37"},
-    {file = "gevent-22.10.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:96c56c280e3c43cfd075efd10b250350ed5ffd3c1514ec99a080b1b92d7c8374"},
-    {file = "gevent-22.10.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c144e08dfad4106effc043a026e5d0c0eff6ad031904c70bf5090c63f3a6a7"},
-    {file = "gevent-22.10.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:018f93de7d5318d2fb440f846839a4464738468c3476d5c9cf7da45bb71c18bd"},
-    {file = "gevent-22.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ed2346eb9dc4344f9cb0d7963ce5b74fe16fdd031a2809bb6c2b6eba7ebcd5"},
-    {file = "gevent-22.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:84c517e33ed604fa06b7d756dc0171169cc12f7fdd68eb7b17708a62eebf4516"},
-    {file = "gevent-22.10.2-cp37-cp37m-win32.whl", hash = "sha256:4114f0f439f0b547bb6f1d474fee99ddb46736944ad2207cef3771828f6aa358"},
-    {file = "gevent-22.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:0d581f22a5be6281b11ad6309b38b18f0638cf896931223cbaa5adb904826ef6"},
-    {file = "gevent-22.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2929377c8ebfb6f4d868d161cd8de2ea6b9f6c7a5fcd4f78bcd537319c16190b"},
-    {file = "gevent-22.10.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:efc003b6c1481165af61f0aeac248e0a9ac8d880bb3acbe469b448674b2d5281"},
-    {file = "gevent-22.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db562a8519838bddad0c439a2b12246bab539dd50e299ea7ff3644274a33b6a5"},
-    {file = "gevent-22.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1472012493ca1fac103f700d309cb6ef7964dcdb9c788d1768266e77712f5e49"},
-    {file = "gevent-22.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c04ee32c11e9fcee47c1b431834878dc987a7a2cc4fe126ddcae3bad723ce89"},
-    {file = "gevent-22.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8729129edef2637a8084258cb9ec4e4d5ca45d97ac77aa7a6ff19ccb530ab731"},
-    {file = "gevent-22.10.2-cp38-cp38-win32.whl", hash = "sha256:ae90226074a6089371a95f20288431cd4b3f6b0b096856afd862e4ac9510cddd"},
-    {file = "gevent-22.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:494c7f29e94df9a1c3157d67bb7edfa32a46eed786e04d9ee68d39f375e30001"},
-    {file = "gevent-22.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:58898dbabb5b11e4d0192aae165ad286dc6742c543e1be9d30dc82753547c508"},
-    {file = "gevent-22.10.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:4197d423e198265eef39a0dea286ef389da9148e070310f34455ecee8172c391"},
-    {file = "gevent-22.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da4183f0b9d9a1e25e1758099220d32c51cc2c6340ee0dea3fd236b2b37598e4"},
-    {file = "gevent-22.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5488eba6a568b4d23c072113da4fc0feb1b5f5ede7381656dc913e0d82204e2"},
-    {file = "gevent-22.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319d8b1699b7b8134de66d656cd739b308ab9c45ace14d60ae44de7775b456c9"},
-    {file = "gevent-22.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f3329bedbba4d3146ae58c667e0f9ac1e6f1e1e6340c7593976cdc60aa7d1a47"},
-    {file = "gevent-22.10.2-cp39-cp39-win32.whl", hash = "sha256:172caa66273315f283e90a315921902cb6549762bdcb0587fd60cb712a9d6263"},
-    {file = "gevent-22.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:323b207b281ba0405fea042067fa1a61662e5ac0d574ede4ebbda03efd20c350"},
-    {file = "gevent-22.10.2-pp27-pypy_73-win_amd64.whl", hash = "sha256:ed7f16613eebf892a6a744d7a4a8f345bc6f066a0ff3b413e2479f9c0a180193"},
-    {file = "gevent-22.10.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a47a4e77e2bc668856aad92a0b8de7ee10768258d93cd03968e6c7ba2e832f76"},
-    {file = "gevent-22.10.2.tar.gz", hash = "sha256:1ca01da176ee37b3527a2702f7d40dbc9ffb8cfc7be5a03bfa4f9eec45e55c46"},
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "gevent-23.9.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771"},
+    {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69"},
+    {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535"},
+    {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a"},
+    {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39"},
+    {file = "gevent-23.9.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae"},
+    {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6"},
+    {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7"},
+    {file = "gevent-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e"},
+    {file = "gevent-23.9.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c"},
+    {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648"},
+    {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07"},
+    {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9"},
+    {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011"},
+    {file = "gevent-23.9.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7"},
+    {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71"},
+    {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e"},
+    {file = "gevent-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a"},
+    {file = "gevent-23.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904"},
+    {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b"},
+    {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e"},
+    {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599"},
+    {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea"},
+    {file = "gevent-23.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599"},
+    {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303"},
+    {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d"},
+    {file = "gevent-23.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1"},
+    {file = "gevent-23.9.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe"},
+    {file = "gevent-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5"},
+    {file = "gevent-23.9.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397"},
+    {file = "gevent-23.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507"},
+    {file = "gevent-23.9.1-cp38-cp38-win32.whl", hash = "sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a"},
+    {file = "gevent-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f"},
+    {file = "gevent-23.9.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a"},
+    {file = "gevent-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653"},
+    {file = "gevent-23.9.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd"},
+    {file = "gevent-23.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543"},
+    {file = "gevent-23.9.1-cp39-cp39-win32.whl", hash = "sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2"},
+    {file = "gevent-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b"},
+    {file = "gevent-23.9.1.tar.gz", hash = "sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34"},
 ]
 
 [package.dependencies]
 cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
-greenlet = {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\""}
-setuptools = "*"
+greenlet = [
+    {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
+    {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""},
+]
 "zope.event" = "*"
 "zope.interface" = "*"
 
 [package.extras]
 dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"]
-docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"]
+docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"]
 monitor = ["psutil (>=5.7.0)"]
-recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"]
-test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"]
+recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"]
+test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests", "setuptools"]
 
 [[package]]
 name = "google-auth"
-version = "2.23.0"
+version = "2.23.1"
 description = "Google Authentication Library"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "google-auth-2.23.0.tar.gz", hash = "sha256:753a26312e6f1eaeec20bc6f2644a10926697da93446e1f8e24d6d32d45a922a"},
-    {file = "google_auth-2.23.0-py2.py3-none-any.whl", hash = "sha256:2cec41407bd1e207f5b802638e32bb837df968bb5c05f413d0fa526fac4cf7a7"},
+    {file = "google-auth-2.23.1.tar.gz", hash = "sha256:d38bdf4fa1e7c5a35e574861bce55784fd08afadb4e48f99f284f1e487ce702d"},
+    {file = "google_auth-2.23.1-py2.py3-none-any.whl", hash = "sha256:9800802266366a2a87890fb2d04923fc0c0d4368af0b86db18edd94a62386ea1"},
 ]
 
 [package.dependencies]
 cachetools = ">=2.0.0,<6.0"
 pyasn1-modules = ">=0.2.1"
 rsa = ">=3.1.4,<5"
-urllib3 = "<2.0"
+urllib3 = ">=2.0.5"
 
 [package.extras]
 aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"]
@@ -964,7 +930,6 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"]
 name = "google-auth-oauthlib"
 version = "1.0.0"
 description = "Google Authentication Library"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -983,7 +948,6 @@ tool = ["click (>=6.0.0)"]
 name = "google-pasta"
 version = "0.2.0"
 description = "pasta is an AST-based Python refactoring library"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -999,7 +963,6 @@ six = "*"
 name = "greenlet"
 version = "2.0.2"
 description = "Lightweight in-process concurrent programming"
-category = "main"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
 files = [
@@ -1008,6 +971,7 @@ files = [
     {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"},
     {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"},
     {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"},
+    {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"},
     {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"},
     {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"},
     {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"},
@@ -1016,6 +980,7 @@ files = [
     {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"},
     {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"},
     {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"},
+    {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"},
     {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"},
     {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"},
     {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"},
@@ -1045,6 +1010,7 @@ files = [
     {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"},
     {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"},
     {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"},
+    {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"},
     {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"},
     {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"},
     {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"},
@@ -1053,6 +1019,7 @@ files = [
     {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"},
     {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"},
     {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"},
+    {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"},
     {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"},
     {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"},
     {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"},
@@ -1069,11 +1036,81 @@ files = [
 docs = ["Sphinx", "docutils (<0.18)"]
 test = ["objgraph", "psutil"]
 
+[[package]]
+name = "greenlet"
+version = "3.0.0rc3"
+description = "Lightweight in-process concurrent programming"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a2affddff9b2f846f40799673e41b29f0500582415c860fca8f146858e9de1a"},
+    {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd00046dfd00767fce18f9933658d126652a500caf7af9dbfbd43818e4b484c2"},
+    {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e787b00002cef3b98c7cf700fb85c2c01b0d202b1c6731706e5baa4b3325aa1e"},
+    {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ffc7538bc66766a8b551888903d415773481c4bd13560a4fb24887222e3cc9"},
+    {file = "greenlet-3.0.0rc3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dde5deb355b34bbf44b15789e27c56862f51f417207be49eedc58fce34681fe6"},
+    {file = "greenlet-3.0.0rc3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fb703a102a02361a0cc6a3d9a7958e1584fdeb536bd37ca9aca529d3356bedd"},
+    {file = "greenlet-3.0.0rc3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f8661d14d3e07f2ceeb850e4cbcc7114bdf90a8dc82d63d37b08a50bb6955a77"},
+    {file = "greenlet-3.0.0rc3-cp310-cp310-win_amd64.whl", hash = "sha256:997456b74efee91ceeb39d63818909da5dbb712a07f7742f4378986ac3473463"},
+    {file = "greenlet-3.0.0rc3-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:d3cd3957af8cec1fcfd87d92ca71b7d434d798036e14ae878f9ab1e07d99da0d"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:895b689fc52a5bc402f8d624705110df5c265b1410ffe8e0769a66db9d2e7851"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a84a88422b5a0360fae57ad6b3b20fc17c9462880929810b0a26ee43aa05982e"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d79cf299ba1996d8a4f133b317e709a0a3ce87181308280e40664e12cb512c54"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9789aea735004eba559c7919a73a3b475d0c28e2c1e9de464c6bc761bf69f4"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66790e1537382e53bce64de3a695d1b12a04b00104df45f7ef472a10561936c2"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:68349987bf2ce274953f9f9b28458869bd8770a0c5461e1ef91d8107b1bae361"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30ffaa6c020a615c8f4be3abfc6029982fda026a3bf9a6dc7205afb033251506"},
+    {file = "greenlet-3.0.0rc3-cp311-cp311-win_amd64.whl", hash = "sha256:864619b058f573058cd77f6944cf63d7f42157fe30be494798721bd8ac256d7b"},
+    {file = "greenlet-3.0.0rc3-cp311-universal2-macosx_10_9_universal2.whl", hash = "sha256:7c887ecb55374d585d71ff8f9d07c137637694e88fa2b5d5b1450a05ece62ae9"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:686821157368c1c4ef53aa68e6801280010da92ab0e4265dad37003341fca6a1"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:242d56d2d5f6859f0f086ce62555a2c692c8053c89721d41fead5e1e8dffdb36"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81d653ae6c64b85ce4c7bccbea7b630de8799da751b73e55b4c68875b6eb19d6"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beeb5cfbd8f3792c37db4e3c5665aa750d78bbdabe758161a34e7dfe27075e69"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30daee988fc83078b016fa95a7a1f78a7c86534a44238748b9748675814eb1dc"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:977898b8c24159467c66ed1a8f62aacd33f3d85f852cf413d0d2e2a87a6b3091"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:097a2f75c79c3fa76fea2e5d48a637233722fe72a5ebb1213c55f0a0898f481c"},
+    {file = "greenlet-3.0.0rc3-cp312-cp312-win_amd64.whl", hash = "sha256:5770d43b08dfa10f4460c1bd51f8c80e6f2c47611054e9fb80d4d7976d07e560"},
+    {file = "greenlet-3.0.0rc3-cp312-universal2-macosx_10_9_universal2.whl", hash = "sha256:f33e7ff85775cb0ec6abb0950ffc631960bae5a203da38166fc3dfde826e0d0a"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f6d1ce31a1db5102a42b4afa609af330edfd8a81d10faba3e47ae33a07cbdf"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e651fa59263f7ff1d4657b086c48cfe7e26db2a36e2d74069f3b5aeab478e6"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef7c6e49a9a020d56349c6a769352709bfbe35d3ee7f98bd5efcac6cedbdc162"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5585bf8d1d2d3712010ee74988c2ed85c54b127b97f2778fbdcc5b3ea8e801a2"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c706041cd92e1b9d2b602eaa31e94aad14453bdbf186ce77530f25167c173a0e"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:37213e72058d2e6231d18417adc63c698c040fbb47dc59a3fd633973214ab1ab"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:95bc6ec8dd73f8f36e9dfc61a7fa5a2819d1cd52d0bfdb70a43434d6b2aeb239"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-win32.whl", hash = "sha256:e83c4c7a0814dcfd7e2fe4b74a371f3ce489b62ff02e81d0c5cacc8ba4750395"},
+    {file = "greenlet-3.0.0rc3-cp37-cp37m-win_amd64.whl", hash = "sha256:4c35608918f331256be199d3712552fa8a1d12f87ac171a86a31488c60d298f5"},
+    {file = "greenlet-3.0.0rc3-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:215bdb33e85fd89fe55f9984dc6f0a96b5774bace663e1a6d051e65d66170ef8"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69db00f775ed9d233f53ef67c66ea40a7add0c0929eb528f633982e27595dd37"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fcc7162944c2fedfb2253ca2171267e016a3b065c73369d0d4a27f601e7f162"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c0082d7b83312c59127811367089f812f8f1386fad7e8cf321fd732b4a6ace6"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66f1131c17dba115ea7cb3b257b6751b3c4cfd324f2121447e2483f57abbbf3c"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0c5508582339090b99e2863a157fc2708ab9c8b5cd21619bdcb04edcdc6c28d"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f1c9ad8d6500f7b142a94054281d9628bc8652a14b0923d02e0dfd87392fbc74"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bd586284bbf18ca3068e1fcc67ef54538e1bb74cb605ebdac9e62048237839f5"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-win32.whl", hash = "sha256:1c16f1bbaf9c75dfac3e52bb778d2fd6099fd5aa59fafa678eca5853eedd99ec"},
+    {file = "greenlet-3.0.0rc3-cp38-cp38-win_amd64.whl", hash = "sha256:e388ceb55b8f3f388afea4d4a17a64b619040f0e8e9fa3e17e7c34f4d0fbe103"},
+    {file = "greenlet-3.0.0rc3-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:68bd35ad9f99df0ef18836fd0fb34278dca6b3350bdcf1e8809822fc4f57a82e"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:256b748fc1e6c97012f217e0a403116cb0dd369bf1cff51c07a9c52899d4a8a8"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4936e6e051932848c4b237a874da8dbb47bfbb5ae5104497fb78c4f4cf184989"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a31b3a4bd10c540a7eb7d4b43d16779813ca4c79b615ed6d4ebf0e5a782d9fa0"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6f8253fdb00e74b928ab5d04f88ddbc8beb0cc26aa978bb4a12c1513166d481"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a7831d04a0f8a14645c010e3fb3fa36b8d2df304dd837948427ccfec2524ddf"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae84d2f2658990f29df4ea753061b25c337bd70f805128af328098e5b8afc454"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cd51cc2528a2985f3bc0564c1b1ce5b2e6fa4ee9924503010428256fa95b0e3c"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-win32.whl", hash = "sha256:e8698f341e78dd0f149511929e92d1507cc26647f047db13987169d244db10fb"},
+    {file = "greenlet-3.0.0rc3-cp39-cp39-win_amd64.whl", hash = "sha256:f059457db4e2ae4a4fdae455453c5e5765aa08efcb804e2a106c69c31bd438ba"},
+    {file = "greenlet-3.0.0rc3-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:c80cac2776df3dd08f27b7338f467a62ee6cb29668a8f4f408b8da1f981aae9e"},
+    {file = "greenlet-3.0.0rc3.tar.gz", hash = "sha256:0df5c2ad154f457fd372e39723493b3df519330a4c1bff3ca901be66130f379b"},
+]
+
+[package.extras]
+docs = ["Sphinx"]
+test = ["objgraph", "psutil"]
+
 [[package]]
 name = "grpcio"
 version = "1.58.0"
 description = "HTTP/2-based RPC framework"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1131,7 +1168,6 @@ protobuf = ["grpcio-tools (>=1.58.0)"]
 name = "h11"
 version = "0.14.0"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1143,7 +1179,6 @@ files = [
 name = "h5py"
 version = "3.9.0"
 description = "Read and write HDF5 files from Python"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1177,7 +1212,6 @@ numpy = ">=1.17.3"
 name = "httpcore"
 version = "0.17.3"
 description = "A minimal low-level HTTP client."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1189,17 +1223,16 @@ files = [
 anyio = ">=3.0,<5.0"
 certifi = "*"
 h11 = ">=0.13,<0.15"
-sniffio = ">=1.0.0,<2.0.0"
+sniffio = "==1.*"
 
 [package.extras]
 http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
 
 [[package]]
 name = "httptools"
 version = "0.6.0"
 description = "A collection of framework independent HTTP protocol utils."
-category = "dev"
 optional = false
 python-versions = ">=3.5.0"
 files = [
@@ -1247,7 +1280,6 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
 name = "httpx"
 version = "0.24.1"
 description = "The next generation HTTP client."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1263,15 +1295,14 @@ sniffio = "*"
 
 [package.extras]
 brotli = ["brotli", "brotlicffi"]
-cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
 http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
 
 [[package]]
 name = "identify"
 version = "2.5.29"
 description = "File identification library for Python"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1286,7 +1317,6 @@ license = ["ukkonen"]
 name = "idna"
 version = "3.4"
 description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -1298,7 +1328,6 @@ files = [
 name = "importlib-metadata"
 version = "6.8.0"
 description = "Read metadata from Python packages"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1318,7 +1347,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs
 name = "iniconfig"
 version = "2.0.0"
 description = "brain-dead simple config-ini parsing"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1330,7 +1358,6 @@ files = [
 name = "ipython"
 version = "8.12.2"
 description = "IPython: Productive Interactive Computing"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1370,7 +1397,6 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa
 name = "isort"
 version = "5.12.0"
 description = "A Python utility / library to sort Python imports."
-category = "dev"
 optional = false
 python-versions = ">=3.8.0"
 files = [
@@ -1388,7 +1414,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
 name = "jax"
 version = "0.4.13"
 description = "Differentiate, compile, and transform Numpy code."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1419,7 +1444,6 @@ tpu = ["jaxlib (==0.4.13)", "libtpu-nightly (==0.1.dev20230622)"]
 name = "jedi"
 version = "0.19.0"
 description = "An autocompletion tool for Python that can be used for text editors."
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1439,7 +1463,6 @@ testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
 name = "jinja2"
 version = "3.1.2"
 description = "A very fast and expressive template engine."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1457,7 +1480,6 @@ i18n = ["Babel (>=2.7)"]
 name = "joblib"
 version = "1.3.2"
 description = "Lightweight pipelining with Python functions"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1469,21 +1491,32 @@ files = [
 name = "keras"
 version = "2.12.0"
 description = "Deep learning for humans."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
     {file = "keras-2.12.0-py2.py3-none-any.whl", hash = "sha256:35c39534011e909645fb93515452e98e1a0ce23727b55d4918b9c58b2308c15e"},
 ]
 
+[[package]]
+name = "keras"
+version = "2.14.0"
+description = "Deep learning for humans."
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "keras-2.14.0-py3-none-any.whl", hash = "sha256:d7429d1d2131cc7eb1f2ea2ec330227c7d9d38dab3dfdf2e78defee4ecc43fcd"},
+    {file = "keras-2.14.0.tar.gz", hash = "sha256:22788bdbc86d9988794fe9703bb5205141da797c4faeeb59497c58c3d94d34ed"},
+]
+
 [[package]]
 name = "libclang"
 version = "16.0.6"
 description = "Clang Python Bindings, mirrored from the official LLVM repo: https://github.com/llvm/llvm-project/tree/main/clang/bindings/python, to make the installation process easier."
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
+    {file = "libclang-16.0.6-1-py2.py3-none-manylinux2014_aarch64.whl", hash = "sha256:88bc7e7b393c32e41e03ba77ef02fdd647da1f764c2cd028e69e0837080b79f6"},
+    {file = "libclang-16.0.6-1-py2.py3-none-manylinux2014_armv7l.whl", hash = "sha256:d80ed5827736ed5ec2bcedf536720476fd9d4fa4c79ef0cb24aea4c59332f361"},
     {file = "libclang-16.0.6-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:da9e47ebc3f0a6d90fb169ef25f9fbcd29b4a4ef97a8b0e3e3a17800af1423f4"},
     {file = "libclang-16.0.6-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1a5ad1e895e5443e205568c85c04b4608e4e973dae42f4dfd9cb46c81d1486b"},
     {file = "libclang-16.0.6-py2.py3-none-manylinux2010_x86_64.whl", hash = "sha256:9dcdc730939788b8b69ffd6d5d75fe5366e3ee007f1e36a99799ec0b0c001492"},
@@ -1497,20 +1530,18 @@ files = [
 
 [[package]]
 name = "lit"
-version = "16.0.6"
+version = "17.0.1"
 description = "A Software Testing Tool"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
-    {file = "lit-16.0.6.tar.gz", hash = "sha256:84623c9c23b6b14763d637f4e63e6b721b3446ada40bf7001d8fee70b8e77a9a"},
+    {file = "lit-17.0.1.tar.gz", hash = "sha256:44867ae576b5790567b120bc3f0d9f021dac9424d1b0840c75acd85f4610ac04"},
 ]
 
 [[package]]
 name = "markdown"
 version = "3.4.4"
 description = "Python implementation of John Gruber's Markdown."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1529,7 +1560,6 @@ testing = ["coverage", "pyyaml"]
 name = "markupsafe"
 version = "2.1.3"
 description = "Safely add untrusted strings to HTML/XML markup."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1553,6 +1583,16 @@ files = [
     {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
     {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
     {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
+    {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
     {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
     {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
     {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@@ -1589,7 +1629,6 @@ files = [
 name = "marshmallow"
 version = "3.20.1"
 description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1610,7 +1649,6 @@ tests = ["pytest", "pytz", "simplejson"]
 name = "matplotlib-inline"
 version = "0.1.6"
 description = "Inline Matplotlib backend for Jupyter"
-category = "dev"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -1625,7 +1663,6 @@ traitlets = "*"
 name = "ml-dtypes"
 version = "0.2.0"
 description = ""
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1652,7 +1689,7 @@ files = [
 numpy = [
     {version = ">1.20", markers = "python_version <= \"3.9\""},
     {version = ">=1.23.3", markers = "python_version > \"3.10\""},
-    {version = ">=1.21.2", markers = "python_version > \"3.9\""},
+    {version = ">=1.21.2", markers = "python_version > \"3.9\" and python_version <= \"3.10\""},
 ]
 
 [package.extras]
@@ -1662,7 +1699,6 @@ dev = ["absl-py", "pyink", "pylint (>=2.6.0)", "pytest", "pytest-xdist"]
 name = "mpmath"
 version = "1.3.0"
 description = "Python library for arbitrary-precision floating-point arithmetic"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -1680,7 +1716,6 @@ tests = ["pytest (>=4.6)"]
 name = "multidict"
 version = "6.0.4"
 description = "multidict implementation"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -1764,7 +1799,6 @@ files = [
 name = "mypy-extensions"
 version = "1.0.0"
 description = "Type system extensions for programs checked with the mypy type checker."
-category = "dev"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -1776,7 +1810,6 @@ files = [
 name = "networkx"
 version = "3.1"
 description = "Python package for creating and manipulating graphs and networks"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1795,7 +1828,6 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"]
 name = "nodeenv"
 version = "1.8.0"
 description = "Node.js virtual environment builder"
-category = "dev"
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
 files = [
@@ -1810,7 +1842,6 @@ setuptools = "*"
 name = "numpy"
 version = "1.23.5"
 description = "NumPy is the fundamental package for array computing with Python."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -1844,11 +1875,47 @@ files = [
     {file = "numpy-1.23.5.tar.gz", hash = "sha256:1b1766d6f397c18153d40015ddfc79ddb715cabadc04d2d228d4e5a8bc4ded1a"},
 ]
 
+[[package]]
+name = "numpy"
+version = "1.24.4"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"},
+    {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"},
+    {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"},
+    {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"},
+    {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"},
+    {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"},
+    {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"},
+    {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"},
+    {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"},
+    {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"},
+    {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"},
+    {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"},
+    {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"},
+    {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"},
+    {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"},
+    {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"},
+    {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"},
+    {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"},
+    {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"},
+    {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"},
+    {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"},
+    {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"},
+    {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"},
+    {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"},
+    {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"},
+    {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"},
+    {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"},
+    {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"},
+]
+
 [[package]]
 name = "nvidia-cublas-cu11"
 version = "11.10.3.66"
 description = "CUBLAS native runtime libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1864,7 +1931,6 @@ wheel = "*"
 name = "nvidia-cuda-cupti-cu11"
 version = "11.7.101"
 description = "CUDA profiling tools runtime libs."
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1880,7 +1946,6 @@ wheel = "*"
 name = "nvidia-cuda-nvrtc-cu11"
 version = "11.7.99"
 description = "NVRTC native runtime libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1897,7 +1962,6 @@ wheel = "*"
 name = "nvidia-cuda-runtime-cu11"
 version = "11.7.99"
 description = "CUDA Runtime native Libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1913,7 +1977,6 @@ wheel = "*"
 name = "nvidia-cudnn-cu11"
 version = "8.5.0.96"
 description = "cuDNN runtime libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1929,7 +1992,6 @@ wheel = "*"
 name = "nvidia-cufft-cu11"
 version = "10.9.0.58"
 description = "CUFFT native runtime libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1941,7 +2003,6 @@ files = [
 name = "nvidia-curand-cu11"
 version = "10.2.10.91"
 description = "CURAND native runtime libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1957,7 +2018,6 @@ wheel = "*"
 name = "nvidia-cusolver-cu11"
 version = "11.4.0.1"
 description = "CUDA solver native runtime libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1974,7 +2034,6 @@ wheel = "*"
 name = "nvidia-cusparse-cu11"
 version = "11.7.4.91"
 description = "CUSPARSE native runtime libraries"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -1990,7 +2049,6 @@ wheel = "*"
 name = "nvidia-nccl-cu11"
 version = "2.14.3"
 description = "NVIDIA Collective Communication Library (NCCL) Runtime"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -2001,7 +2059,6 @@ files = [
 name = "nvidia-nvtx-cu11"
 version = "11.7.91"
 description = "NVIDIA Tools Extension"
-category = "dev"
 optional = false
 python-versions = ">=3"
 files = [
@@ -2017,7 +2074,6 @@ wheel = "*"
 name = "oauthlib"
 version = "3.2.2"
 description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2034,7 +2090,6 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
 name = "opt-einsum"
 version = "3.3.0"
 description = "Optimizing numpys einsum function"
-category = "dev"
 optional = false
 python-versions = ">=3.5"
 files = [
@@ -2053,7 +2108,6 @@ tests = ["pytest", "pytest-cov", "pytest-pep8"]
 name = "packaging"
 version = "23.1"
 description = "Core utilities for Python packages"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2065,7 +2119,6 @@ files = [
 name = "parso"
 version = "0.8.3"
 description = "A Python Parser"
-category = "dev"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2081,7 +2134,6 @@ testing = ["docopt", "pytest (<6.0.0)"]
 name = "pathspec"
 version = "0.11.2"
 description = "Utility library for gitignore style pattern matching of file paths."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2093,7 +2145,6 @@ files = [
 name = "pexpect"
 version = "4.8.0"
 description = "Pexpect allows easy control of interactive console applications."
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2108,7 +2159,6 @@ ptyprocess = ">=0.5"
 name = "pickleshare"
 version = "0.7.5"
 description = "Tiny 'shelve'-like database with concurrency support"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2120,7 +2170,6 @@ files = [
 name = "platformdirs"
 version = "3.10.0"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2136,7 +2185,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
 name = "pluggy"
 version = "1.3.0"
 description = "plugin and hook calling mechanisms for python"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2152,7 +2200,6 @@ testing = ["pytest", "pytest-benchmark"]
 name = "pre-commit"
 version = "2.21.0"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2171,7 +2218,6 @@ virtualenv = ">=20.10.0"
 name = "prompt-toolkit"
 version = "3.0.39"
 description = "Library for building powerful interactive command lines in Python"
-category = "dev"
 optional = false
 python-versions = ">=3.7.0"
 files = [
@@ -2186,7 +2232,6 @@ wcwidth = "*"
 name = "protobuf"
 version = "4.24.3"
 description = ""
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2209,7 +2254,6 @@ files = [
 name = "psutil"
 version = "5.9.5"
 description = "Cross-platform lib for process and system monitoring in Python."
-category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -2236,7 +2280,6 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
 name = "ptyprocess"
 version = "0.7.0"
 description = "Run a subprocess in a pseudo terminal"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2248,7 +2291,6 @@ files = [
 name = "pure-eval"
 version = "0.2.2"
 description = "Safely evaluate AST nodes without side effects"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2263,7 +2305,6 @@ tests = ["pytest"]
 name = "pyasn1"
 version = "0.5.0"
 description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
-category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
 files = [
@@ -2275,7 +2316,6 @@ files = [
 name = "pyasn1-modules"
 version = "0.3.0"
 description = "A collection of ASN.1-based protocols modules"
-category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
 files = [
@@ -2290,7 +2330,6 @@ pyasn1 = ">=0.4.6,<0.6.0"
 name = "pycparser"
 version = "2.21"
 description = "C parser in Python"
-category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -2300,19 +2339,18 @@ files = [
 
 [[package]]
 name = "pydantic"
-version = "2.3.0"
+version = "2.4.1"
 description = "Data validation using Python type hints"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"},
-    {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"},
+    {file = "pydantic-2.4.1-py3-none-any.whl", hash = "sha256:2b2240c8d54bb8f84b88e061fac1bdfa1761c2859c367f9d3afe0ec2966deddc"},
+    {file = "pydantic-2.4.1.tar.gz", hash = "sha256:b172505886028e4356868d617d2d1a776d7af1625d1313450fd51bdd19d9d61f"},
 ]
 
 [package.dependencies]
 annotated-types = ">=0.4.0"
-pydantic-core = "2.6.3"
+pydantic-core = "2.10.1"
 typing-extensions = ">=4.6.1"
 
 [package.extras]
@@ -2320,118 +2358,117 @@ email = ["email-validator (>=2.0.0)"]
 
 [[package]]
 name = "pydantic-core"
-version = "2.6.3"
+version = "2.10.1"
 description = ""
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"},
-    {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"},
-    {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"},
-    {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"},
-    {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"},
-    {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"},
-    {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"},
-    {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"},
-    {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"},
-    {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"},
-    {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"},
-    {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"},
-    {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"},
-    {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"},
-    {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"},
-    {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"},
-    {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"},
-    {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"},
-    {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"},
-    {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"},
-    {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"},
-    {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"},
-    {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"},
-    {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"},
-    {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"},
-    {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"},
-    {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"},
-    {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"},
-    {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"},
-    {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"},
-    {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"},
-    {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"},
+    {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"},
+    {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"},
+    {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"},
+    {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"},
+    {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"},
+    {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"},
+    {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"},
+    {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"},
+    {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"},
+    {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"},
+    {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"},
+    {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"},
+    {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"},
+    {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"},
+    {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"},
+    {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"},
+    {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"},
+    {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"},
+    {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"},
+    {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"},
+    {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"},
+    {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"},
+    {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"},
+    {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"},
+    {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"},
+    {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"},
+    {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"},
+    {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"},
+    {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"},
+    {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"},
+    {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"},
 ]
 
 [package.dependencies]
@@ -2441,7 +2478,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
 name = "pygments"
 version = "2.16.1"
 description = "Pygments is a syntax highlighting package written in Python."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2454,14 +2490,13 @@ plugins = ["importlib-metadata"]
 
 [[package]]
 name = "pyright"
-version = "1.1.327"
+version = "1.1.328"
 description = "Command line wrapper for pyright"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "pyright-1.1.327-py3-none-any.whl", hash = "sha256:3462cda239e9140276238bbdbd0b59d77406f1c2e14d8cb8c20c8e25639c6b3c"},
-    {file = "pyright-1.1.327.tar.gz", hash = "sha256:ba74148ad64f22020dbbed6781c4bdb38ecb8a7ca90dc3c87a4f08d1c0e11592"},
+    {file = "pyright-1.1.328-py3-none-any.whl", hash = "sha256:a1861885419a5f0a19e44421d2541ad6bf0b328b1af00a24e014cb5e99d13ef4"},
+    {file = "pyright-1.1.328.tar.gz", hash = "sha256:24efd38220c195c75d4cda94764c4137e0e6034ca9e9707ec1852ca48503251c"},
 ]
 
 [package.dependencies]
@@ -2475,7 +2510,6 @@ dev = ["twine (>=3.4.1)"]
 name = "pytest"
 version = "7.4.2"
 description = "pytest: simple powerful testing with Python"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2498,7 +2532,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
 name = "pytest-asyncio"
 version = "0.20.3"
 description = "Pytest support for asyncio"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2517,7 +2550,6 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy
 name = "pytest-cov"
 version = "4.1.0"
 description = "Pytest plugin for measuring coverage."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2536,7 +2568,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
 name = "pytest-xdist"
 version = "3.3.1"
 description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2558,7 +2589,6 @@ testing = ["filelock"]
 name = "python-dateutil"
 version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
-category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 files = [
@@ -2573,7 +2603,6 @@ six = ">=1.5"
 name = "python-dotenv"
 version = "1.0.0"
 description = "Read key-value pairs from a .env file and set them as environment variables"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2588,7 +2617,6 @@ cli = ["click (>=5.0)"]
 name = "python-multipart"
 version = "0.0.5"
 description = "A streaming multipart parser for Python"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2602,7 +2630,6 @@ six = ">=1.4.0"
 name = "pyyaml"
 version = "6.0.1"
 description = "YAML parser and emitter for Python"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -2611,6 +2638,7 @@ files = [
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+    {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
     {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
     {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
     {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
@@ -2618,8 +2646,15 @@ files = [
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+    {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
     {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+    {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
     {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
@@ -2636,6 +2671,7 @@ files = [
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+    {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
     {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
     {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
     {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
@@ -2643,6 +2679,7 @@ files = [
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+    {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
     {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
     {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
     {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
@@ -2652,7 +2689,6 @@ files = [
 name = "requests"
 version = "2.31.0"
 description = "Python HTTP for Humans."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2674,7 +2710,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 name = "requests-oauthlib"
 version = "1.3.1"
 description = "OAuthlib authentication support for Requests."
-category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
@@ -2693,7 +2728,6 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
 name = "rsa"
 version = "4.9"
 description = "Pure-Python RSA implementation"
-category = "dev"
 optional = false
 python-versions = ">=3.6,<4"
 files = [
@@ -2708,7 +2742,6 @@ pyasn1 = ">=0.1.3"
 name = "ruff"
 version = "0.0.231"
 description = "An extremely fast Python linter, written in Rust."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2732,38 +2765,37 @@ files = [
 
 [[package]]
 name = "scikit-learn"
-version = "1.3.0"
+version = "1.3.1"
 description = "A set of python modules for machine learning and data mining"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "scikit-learn-1.3.0.tar.gz", hash = "sha256:8be549886f5eda46436b6e555b0e4873b4f10aa21c07df45c4bc1735afbccd7a"},
-    {file = "scikit_learn-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981287869e576d42c682cf7ca96af0c6ac544ed9316328fd0d9292795c742cf5"},
-    {file = "scikit_learn-1.3.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:436aaaae2c916ad16631142488e4c82f4296af2404f480e031d866863425d2a2"},
-    {file = "scikit_learn-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7e28d8fa47a0b30ae1bd7a079519dd852764e31708a7804da6cb6f8b36e3630"},
-    {file = "scikit_learn-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80c08834a473d08a204d966982a62e11c976228d306a2648c575e3ead12111"},
-    {file = "scikit_learn-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:552fd1b6ee22900cf1780d7386a554bb96949e9a359999177cf30211e6b20df6"},
-    {file = "scikit_learn-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79970a6d759eb00a62266a31e2637d07d2d28446fca8079cf9afa7c07b0427f8"},
-    {file = "scikit_learn-1.3.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:850a00b559e636b23901aabbe79b73dc604b4e4248ba9e2d6e72f95063765603"},
-    {file = "scikit_learn-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee04835fb016e8062ee9fe9074aef9b82e430504e420bff51e3e5fffe72750ca"},
-    {file = "scikit_learn-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d953531f5d9f00c90c34fa3b7d7cfb43ecff4c605dac9e4255a20b114a27369"},
-    {file = "scikit_learn-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:151ac2bf65ccf363664a689b8beafc9e6aae36263db114b4ca06fbbbf827444a"},
-    {file = "scikit_learn-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a885a9edc9c0a341cab27ec4f8a6c58b35f3d449c9d2503a6fd23e06bbd4f6a"},
-    {file = "scikit_learn-1.3.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:9877af9c6d1b15486e18a94101b742e9d0d2f343d35a634e337411ddb57783f3"},
-    {file = "scikit_learn-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c470f53cea065ff3d588050955c492793bb50c19a92923490d18fcb637f6383a"},
-    {file = "scikit_learn-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6e2d7389542eae01077a1ee0318c4fec20c66c957f45c7aac0c6eb0fe3c612"},
-    {file = "scikit_learn-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:3a11936adbc379a6061ea32fa03338d4ca7248d86dd507c81e13af428a5bc1db"},
-    {file = "scikit_learn-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:998d38fcec96584deee1e79cd127469b3ad6fefd1ea6c2dfc54e8db367eb396b"},
-    {file = "scikit_learn-1.3.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:ded35e810438a527e17623ac6deae3b360134345b7c598175ab7741720d7ffa7"},
-    {file = "scikit_learn-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e8102d5036e28d08ab47166b48c8d5e5810704daecf3a476a4282d562be9a28"},
-    {file = "scikit_learn-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7617164951c422747e7c32be4afa15d75ad8044f42e7d70d3e2e0429a50e6718"},
-    {file = "scikit_learn-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:1d54fb9e6038284548072df22fd34777e434153f7ffac72c8596f2d6987110dd"},
+    {file = "scikit-learn-1.3.1.tar.gz", hash = "sha256:1a231cced3ee3fa04756b4a7ab532dc9417acd581a330adff5f2c01ac2831fcf"},
+    {file = "scikit_learn-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3153612ff8d36fa4e35ef8b897167119213698ea78f3fd130b4068e6f8d2da5a"},
+    {file = "scikit_learn-1.3.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6bb9490fdb8e7e00f1354621689187bef3cab289c9b869688f805bf724434755"},
+    {file = "scikit_learn-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7135a03af71138669f19bc96e7d0cc8081aed4b3565cc3b131135d65fc642ba"},
+    {file = "scikit_learn-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d8dee8c1f40eeba49a85fe378bdf70a07bb64aba1a08fda1e0f48d27edfc3e6"},
+    {file = "scikit_learn-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:4d379f2b34096105a96bd857b88601dffe7389bd55750f6f29aaa37bc6272eb5"},
+    {file = "scikit_learn-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14e8775eba072ab10866a7e0596bc9906873e22c4c370a651223372eb62de180"},
+    {file = "scikit_learn-1.3.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:58b0c2490eff8355dc26e884487bf8edaccf2ba48d09b194fb2f3a026dd64f9d"},
+    {file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f66eddfda9d45dd6cadcd706b65669ce1df84b8549875691b1f403730bdef217"},
+    {file = "scikit_learn-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6448c37741145b241eeac617028ba6ec2119e1339b1385c9720dae31367f2be"},
+    {file = "scikit_learn-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c413c2c850241998168bbb3bd1bb59ff03b1195a53864f0b80ab092071af6028"},
+    {file = "scikit_learn-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:52b77cc08bd555969ec5150788ed50276f5ef83abb72e6f469c5b91a0009bbca"},
+    {file = "scikit_learn-1.3.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a683394bc3f80b7c312c27f9b14ebea7766b1f0a34faf1a2e9158d80e860ec26"},
+    {file = "scikit_learn-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15d964d9eb181c79c190d3dbc2fff7338786bf017e9039571418a1d53dab236"},
+    {file = "scikit_learn-1.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ce9233cdf0cdcf0858a5849d306490bf6de71fa7603a3835124e386e62f2311"},
+    {file = "scikit_learn-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:1ec668ce003a5b3d12d020d2cde0abd64b262ac5f098b5c84cf9657deb9996a8"},
+    {file = "scikit_learn-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccbbedae99325628c1d1cbe3916b7ef58a1ce949672d8d39c8b190e10219fd32"},
+    {file = "scikit_learn-1.3.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:845f81c7ceb4ea6bac64ab1c9f2ce8bef0a84d0f21f3bece2126adcc213dfecd"},
+    {file = "scikit_learn-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8454d57a22d856f1fbf3091bd86f9ebd4bff89088819886dc0c72f47a6c30652"},
+    {file = "scikit_learn-1.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d993fb70a1d78c9798b8f2f28705bfbfcd546b661f9e2e67aa85f81052b9c53"},
+    {file = "scikit_learn-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:66f7bb1fec37d65f4ef85953e1df5d3c98a0f0141d394dcdaead5a6de9170347"},
 ]
 
 [package.dependencies]
 joblib = ">=1.1.1"
-numpy = ">=1.17.3"
+numpy = ">=1.17.3,<2.0"
 scipy = ">=1.5.0"
 threadpoolctl = ">=2.0.0"
 
@@ -2777,7 +2809,6 @@ tests = ["black (>=23.3.0)", "matplotlib (>=3.1.3)", "mypy (>=1.3)", "numpydoc (
 name = "scipy"
 version = "1.10.1"
 description = "Fundamental algorithms for scientific computing in Python"
-category = "dev"
 optional = false
 python-versions = "<3.12,>=3.8"
 files = [
@@ -2816,7 +2847,6 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo
 name = "setuptools"
 version = "68.2.2"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2833,7 +2863,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
 name = "six"
 version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
-category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
@@ -2845,7 +2874,6 @@ files = [
 name = "sniffio"
 version = "1.3.0"
 description = "Sniff out which async library your code is running under"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -2855,53 +2883,52 @@ files = [
 
 [[package]]
 name = "sqlalchemy"
-version = "2.0.20"
+version = "2.0.21"
 description = "Database Abstraction Library"
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98"},
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e"},
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8"},
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50"},
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed"},
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61"},
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d"},
-    {file = "SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53"},
-    {file = "SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e"},
-    {file = "SQLAlchemy-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3"},
-    {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed"},
-    {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57"},
-    {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897"},
-    {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a"},
-    {file = "SQLAlchemy-2.0.20-cp37-cp37m-win32.whl", hash = "sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459"},
-    {file = "SQLAlchemy-2.0.20-cp37-cp37m-win_amd64.whl", hash = "sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-win32.whl", hash = "sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74"},
-    {file = "SQLAlchemy-2.0.20-cp38-cp38-win_amd64.whl", hash = "sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9"},
-    {file = "SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267"},
-    {file = "SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf"},
-    {file = "SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-win32.whl", hash = "sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4"},
+    {file = "SQLAlchemy-2.0.21-cp310-cp310-win_amd64.whl", hash = "sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-win32.whl", hash = "sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b"},
+    {file = "SQLAlchemy-2.0.21-cp311-cp311-win_amd64.whl", hash = "sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce"},
+    {file = "SQLAlchemy-2.0.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8"},
+    {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec"},
+    {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af"},
+    {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178"},
+    {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01"},
+    {file = "SQLAlchemy-2.0.21-cp37-cp37m-win32.whl", hash = "sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9"},
+    {file = "SQLAlchemy-2.0.21-cp37-cp37m-win_amd64.whl", hash = "sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-win32.whl", hash = "sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6"},
+    {file = "SQLAlchemy-2.0.21-cp38-cp38-win_amd64.whl", hash = "sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-win32.whl", hash = "sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9"},
+    {file = "SQLAlchemy-2.0.21-cp39-cp39-win_amd64.whl", hash = "sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce"},
+    {file = "SQLAlchemy-2.0.21-py3-none-any.whl", hash = "sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce"},
+    {file = "SQLAlchemy-2.0.21.tar.gz", hash = "sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258"},
 ]
 
 [package.dependencies]
@@ -2936,7 +2963,6 @@ sqlcipher = ["sqlcipher3-binary"]
 name = "stack-data"
 version = "0.6.2"
 description = "Extract data from python stack frames and tracebacks for informative displays"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -2956,7 +2982,6 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
 name = "starlette"
 version = "0.31.1"
 description = "The little ASGI library that shines."
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2975,7 +3000,6 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam
 name = "sympy"
 version = "1.12"
 description = "Computer algebra system (CAS) in Python"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -2990,7 +3014,6 @@ mpmath = ">=0.19"
 name = "tensorboard"
 version = "2.12.3"
 description = "TensorBoard lets you watch Tensors Flow"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -3011,11 +3034,34 @@ tensorboard-data-server = ">=0.7.0,<0.8.0"
 werkzeug = ">=1.0.1"
 wheel = ">=0.26"
 
+[[package]]
+name = "tensorboard"
+version = "2.14.0"
+description = "TensorBoard lets you watch Tensors Flow"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "tensorboard-2.14.0-py3-none-any.whl", hash = "sha256:3667f9745d99280836ad673022362c840f60ed8fefd5a3e30bf071f5a8fd0017"},
+]
+
+[package.dependencies]
+absl-py = ">=0.4"
+google-auth = ">=1.6.3,<3"
+google-auth-oauthlib = ">=0.5,<1.1"
+grpcio = ">=1.48.2"
+markdown = ">=2.6.8"
+numpy = ">=1.12.0"
+protobuf = ">=3.19.6"
+requests = ">=2.21.0,<3"
+setuptools = ">=41.0.0"
+tensorboard-data-server = ">=0.7.0,<0.8.0"
+werkzeug = ">=1.0.1"
+wheel = ">=0.26"
+
 [[package]]
 name = "tensorboard-data-server"
 version = "0.7.1"
 description = "Fast data loading for TensorBoard"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3028,7 +3074,6 @@ files = [
 name = "tensorflow-cpu"
 version = "2.12.0"
 description = "TensorFlow is an open source machine learning framework for everyone."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -3070,22 +3115,75 @@ termcolor = ">=1.1.0"
 typing-extensions = ">=3.6.6"
 wrapt = ">=1.11.0,<1.15"
 
+[[package]]
+name = "tensorflow-cpu"
+version = "2.14.0"
+description = "TensorFlow is an open source machine learning framework for everyone."
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "tensorflow_cpu-2.14.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:d5cf976bff9b03123a4810883a1879ed1a3da9d955428bbeef419da99ef6424e"},
+    {file = "tensorflow_cpu-2.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2e5b562a7abe3ec65e678f54690b970df59238b9dd3f29fe499fc689becb050"},
+    {file = "tensorflow_cpu-2.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ee31157d5c5ee7c6a077a5942258f8dfa09e9ab6b4cb422ff82dd666e8982f9b"},
+    {file = "tensorflow_cpu-2.14.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c1f916d2ffa8cc2f6c3e879a2dfde0967d821e22306fbb614880e744101cd77d"},
+    {file = "tensorflow_cpu-2.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8886642da74839b6cadcbc2f748ebdfc20e83b71f2069686485f9a186bff1b07"},
+    {file = "tensorflow_cpu-2.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:f8159ee08ac50b7e88073820919cbdf259813e8c4617c903c90aa94f9ee4f9a7"},
+    {file = "tensorflow_cpu-2.14.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:2655d71d0353924040f603ece88330c713dc8adc9ebc4c58e9d356bb61fb6c14"},
+    {file = "tensorflow_cpu-2.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7f3d192941560fab0993d102917bc6bf00b1b02ea30b67e52f7a15233468bb"},
+    {file = "tensorflow_cpu-2.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ec35d64ffe0f8dc0aba38ce56c8df7d2e9d3ef69bc6d04d1c4f465e0fde8eac"},
+]
+
+[package.dependencies]
+absl-py = ">=1.0.0"
+astunparse = ">=1.6.0"
+flatbuffers = ">=23.5.26"
+gast = ">=0.2.1,<0.5.0 || >0.5.0,<0.5.1 || >0.5.1,<0.5.2 || >0.5.2"
+google-pasta = ">=0.1.1"
+grpcio = ">=1.24.3,<2.0"
+h5py = ">=2.9.0"
+keras = ">=2.14.0,<2.15"
+libclang = ">=13.0.0"
+ml-dtypes = "0.2.0"
+numpy = ">=1.23.5"
+opt-einsum = ">=2.3.2"
+packaging = "*"
+protobuf = ">=3.20.3,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev"
+setuptools = "*"
+six = ">=1.12.0"
+tensorboard = ">=2.14,<2.15"
+tensorflow-estimator = ">=2.14.0,<2.15"
+tensorflow-io-gcs-filesystem = ">=0.23.1"
+termcolor = ">=1.1.0"
+typing-extensions = ">=3.6.6"
+wrapt = ">=1.11.0,<1.15"
+
+[package.extras]
+and-cuda = ["nvidia-cublas-cu11 (==11.11.3.6)", "nvidia-cuda-cupti-cu11 (==11.8.87)", "nvidia-cuda-nvcc-cu11 (==11.8.89)", "nvidia-cuda-runtime-cu11 (==11.8.89)", "nvidia-cudnn-cu11 (==8.7.0.84)", "nvidia-cufft-cu11 (==10.9.0.58)", "nvidia-curand-cu11 (==10.3.0.86)", "nvidia-cusolver-cu11 (==11.4.1.48)", "nvidia-cusparse-cu11 (==11.7.5.86)", "nvidia-nccl-cu11 (==2.16.5)", "tensorrt (==8.5.3.1)"]
+
 [[package]]
 name = "tensorflow-estimator"
 version = "2.12.0"
 description = "TensorFlow Estimator."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
     {file = "tensorflow_estimator-2.12.0-py2.py3-none-any.whl", hash = "sha256:59b191bead4883822de3d63ac02ace11a83bfe6c10d64d0c4dfde75a50e60ca1"},
 ]
 
+[[package]]
+name = "tensorflow-estimator"
+version = "2.14.0"
+description = "TensorFlow Estimator."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "tensorflow_estimator-2.14.0-py2.py3-none-any.whl", hash = "sha256:820bf57c24aa631abb1bbe4371739ed77edb11361d61381fd8e790115ac0fd57"},
+]
+
 [[package]]
 name = "tensorflow-io-gcs-filesystem"
 version = "0.34.0"
 description = "TensorFlow IO"
-category = "dev"
 optional = false
 python-versions = ">=3.7, <3.12"
 files = [
@@ -3121,7 +3219,6 @@ tensorflow-rocm = ["tensorflow-rocm (>=2.13.0,<2.14.0)"]
 name = "termcolor"
 version = "2.3.0"
 description = "ANSI color formatting for output in terminal"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3136,7 +3233,6 @@ tests = ["pytest", "pytest-cov"]
 name = "text-unidecode"
 version = "1.3"
 description = "The most basic Text::Unidecode port"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -3148,7 +3244,6 @@ files = [
 name = "threadpoolctl"
 version = "3.2.0"
 description = "threadpoolctl"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -3160,7 +3255,6 @@ files = [
 name = "tomli"
 version = "2.0.1"
 description = "A lil' TOML parser"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3172,7 +3266,6 @@ files = [
 name = "torch"
 version = "2.0.0"
 description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration"
-category = "dev"
 optional = false
 python-versions = ">=3.8.0"
 files = [
@@ -3226,14 +3319,13 @@ opt-einsum = ["opt-einsum (>=3.3)"]
 
 [[package]]
 name = "traitlets"
-version = "5.10.0"
+version = "5.10.1"
 description = "Traitlets Python configuration system"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "traitlets-5.10.0-py3-none-any.whl", hash = "sha256:417745a96681fbb358e723d5346a547521f36e9bd0d50ba7ab368fff5d67aa54"},
-    {file = "traitlets-5.10.0.tar.gz", hash = "sha256:f584ea209240466e66e91f3c81aa7d004ba4cf794990b0c775938a1544217cd1"},
+    {file = "traitlets-5.10.1-py3-none-any.whl", hash = "sha256:07ab9c5bf8a0499fd7b088ba51be899c90ffc936ffc797d7b6907fc516bcd116"},
+    {file = "traitlets-5.10.1.tar.gz", hash = "sha256:db9c4aa58139c3ba850101913915c042bdba86f7c8a0dda1c6f7f92c5da8e542"},
 ]
 
 [package.extras]
@@ -3244,7 +3336,6 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,
 name = "triton"
 version = "2.0.0"
 description = "A language and compiler for custom Deep Learning operations"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -3282,7 +3373,6 @@ tutorials = ["matplotlib", "pandas", "tabulate"]
 name = "typesystem"
 version = "0.4.1"
 description = "A type system library for Python"
-category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -3298,7 +3388,6 @@ pyyaml = ["pyyaml"]
 name = "typing-extensions"
 version = "4.8.0"
 description = "Backported and Experimental Type Hints for Python 3.8+"
-category = "main"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -3308,26 +3397,25 @@ files = [
 
 [[package]]
 name = "urllib3"
-version = "1.26.16"
+version = "2.0.5"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.7"
 files = [
-    {file = "urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"},
-    {file = "urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"},
+    {file = "urllib3-2.0.5-py3-none-any.whl", hash = "sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e"},
+    {file = "urllib3-2.0.5.tar.gz", hash = "sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594"},
 ]
 
 [package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
 
 [[package]]
 name = "uvicorn"
 version = "0.22.0"
 description = "The lightning-fast ASGI server."
-category = "main"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3342,7 +3430,7 @@ h11 = ">=0.8"
 httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
 python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
 pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
-uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
+uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
 watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
 websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
 
@@ -3353,7 +3441,6 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
 name = "uvloop"
 version = "0.17.0"
 description = "Fast implementation of asyncio event loop on top of libuv"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3398,7 +3485,6 @@ test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "my
 name = "virtualenv"
 version = "20.24.5"
 description = "Virtual Python Environment builder"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3419,7 +3505,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
 name = "watchfiles"
 version = "0.20.0"
 description = "Simple, modern and high performance file watching and code reload in python."
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3454,7 +3539,6 @@ anyio = ">=3.0.0"
 name = "wcwidth"
 version = "0.2.6"
 description = "Measures the displayed width of unicode strings in a terminal"
-category = "dev"
 optional = false
 python-versions = "*"
 files = [
@@ -3466,7 +3550,6 @@ files = [
 name = "websockets"
 version = "11.0.3"
 description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3546,7 +3629,6 @@ files = [
 name = "werkzeug"
 version = "2.3.7"
 description = "The comprehensive WSGI web application library."
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
@@ -3564,7 +3646,6 @@ watchdog = ["watchdog (>=2.3)"]
 name = "wheel"
 version = "0.41.2"
 description = "A built-package format for Python"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3579,7 +3660,6 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"]
 name = "wrapt"
 version = "1.14.1"
 description = "Module for decorators, wrappers and monkey patching."
-category = "dev"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 files = [
@@ -3653,7 +3733,6 @@ files = [
 name = "yarl"
 version = "1.9.2"
 description = "Yet another URL library"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3739,25 +3818,23 @@ multidict = ">=4.0"
 
 [[package]]
 name = "zipp"
-version = "3.16.2"
+version = "3.17.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "dev"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"},
-    {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"},
+    {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"},
+    {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"},
 ]
 
 [package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
 testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
 
 [[package]]
 name = "zope-event"
 version = "5.0"
 description = "Very basic event publishing system"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3776,7 +3853,6 @@ test = ["zope.testrunner"]
 name = "zope-interface"
 version = "6.0"
 description = "Interfaces for Python"
-category = "dev"
 optional = false
 python-versions = ">=3.7"
 files = [
@@ -3831,4 +3907,4 @@ typesystem = ["typesystem"]
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.8,<3.12"
-content-hash = "e40ef7edaf3ec048388665a1093d0f6719048cab9647fff4c70a723bc8b94135"
+content-hash = "9d72a8ecd5ab210360236ef8ccd1522856d6033bf5f40f118bd7e45b841f4c6d"
diff --git a/pyproject.toml b/pyproject.toml
index 0d5248e8..3a8d377f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -77,7 +77,7 @@ aiosqlite = ">=0.11.0,<1.0.0"
 requests = "^2.22"
 Faker = "^8.2"
 SQLAlchemy = { version = "^2.0", extras = ["asyncio"] }
-gevent = "^22.10"
+gevent = "^23.9.1"
 pyright = "^1.1.320"
 
 [tool.poetry.group.schemas]
@@ -94,7 +94,10 @@ optional = true
 
 [tool.poetry.group.ml.dependencies]
 scikit-learn = "^1.3.0"
-tensorflow-cpu = { version = "^2.12.0", platform = "linux"}
+tensorflow-cpu = [
+    { version = "^2.12.0", platform = "linux", python = ">=3.8,<3.9" },
+    { version = "^2.14.0", platform = "linux", python = ">=3.9"}
+]
 torch = "^2.0.0, !=2.0.1"
 
 [tool.black]
@@ -202,6 +205,7 @@ omit = [
     "flama/middleware.py",
     "flama/testclient.py",
 ]
+concurrency = ["thread", "multiprocessing"]
 
 [tool.coverage.report]
 show_missing = true
diff --git a/templates/package-lock.json b/templates/package-lock.json
index 71d55918..5f305f7b 100644
--- a/templates/package-lock.json
+++ b/templates/package-lock.json
@@ -14,7 +14,7 @@
         "tailwindcss": "^3.2.1"
       },
       "devDependencies": {
-        "@babel/core": "^7.19.6",
+        "@babel/core": "^7.23.0",
         "@babel/preset-env": "^7.19.4",
         "@babel/preset-react": "^7.18.6",
         "@babel/preset-typescript": "^7.18.6",
@@ -40,7 +40,7 @@
         "style-loader": "^3.3.1",
         "ts-loader": "^9.4.1",
         "typescript": "^4.8.4",
-        "webpack": "^5.74.0",
+        "webpack": "^5.76.0",
         "webpack-cli": "^4.10.0"
       }
     },
@@ -58,47 +58,48 @@
       }
     },
     "node_modules/@babel/code-frame": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
-      "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+      "version": "7.22.13",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+      "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
       "dev": true,
       "dependencies": {
-        "@babel/highlight": "^7.18.6"
+        "@babel/highlight": "^7.22.13",
+        "chalk": "^2.4.2"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/compat-data": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.4.tgz",
-      "integrity": "sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz",
+      "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz",
-      "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==",
-      "dev": true,
-      "dependencies": {
-        "@ampproject/remapping": "^2.1.0",
-        "@babel/code-frame": "^7.18.6",
-        "@babel/generator": "^7.19.6",
-        "@babel/helper-compilation-targets": "^7.19.3",
-        "@babel/helper-module-transforms": "^7.19.6",
-        "@babel/helpers": "^7.19.4",
-        "@babel/parser": "^7.19.6",
-        "@babel/template": "^7.18.10",
-        "@babel/traverse": "^7.19.6",
-        "@babel/types": "^7.19.4",
-        "convert-source-map": "^1.7.0",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz",
+      "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==",
+      "dev": true,
+      "dependencies": {
+        "@ampproject/remapping": "^2.2.0",
+        "@babel/code-frame": "^7.22.13",
+        "@babel/generator": "^7.23.0",
+        "@babel/helper-compilation-targets": "^7.22.15",
+        "@babel/helper-module-transforms": "^7.23.0",
+        "@babel/helpers": "^7.23.0",
+        "@babel/parser": "^7.23.0",
+        "@babel/template": "^7.22.15",
+        "@babel/traverse": "^7.23.0",
+        "@babel/types": "^7.23.0",
+        "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
-        "json5": "^2.2.1",
-        "semver": "^6.3.0"
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -109,13 +110,14 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz",
-      "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
+      "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.19.4",
+        "@babel/types": "^7.23.0",
         "@jridgewell/gen-mapping": "^0.3.2",
+        "@jridgewell/trace-mapping": "^0.3.17",
         "jsesc": "^2.5.1"
       },
       "engines": {
@@ -162,23 +164,36 @@
       }
     },
     "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.19.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz",
-      "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
+      "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
       "dev": true,
       "dependencies": {
-        "@babel/compat-data": "^7.19.3",
-        "@babel/helper-validator-option": "^7.18.6",
-        "browserslist": "^4.21.3",
-        "semver": "^6.3.0"
+        "@babel/compat-data": "^7.22.9",
+        "@babel/helper-validator-option": "^7.22.15",
+        "browserslist": "^4.21.9",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
       },
       "engines": {
         "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0"
       }
     },
+    "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    },
     "node_modules/@babel/helper-create-class-features-plugin": {
       "version": "7.19.0",
       "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz",
@@ -234,9 +249,9 @@
       }
     },
     "node_modules/@babel/helper-environment-visitor": {
-      "version": "7.18.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
-      "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+      "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
@@ -255,25 +270,25 @@
       }
     },
     "node_modules/@babel/helper-function-name": {
-      "version": "7.19.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
-      "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+      "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
       "dev": true,
       "dependencies": {
-        "@babel/template": "^7.18.10",
-        "@babel/types": "^7.19.0"
+        "@babel/template": "^7.22.15",
+        "@babel/types": "^7.23.0"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-hoist-variables": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
-      "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+      "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -292,34 +307,34 @@
       }
     },
     "node_modules/@babel/helper-module-imports": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
-      "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+      "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.15"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-module-transforms": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz",
-      "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
+      "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-environment-visitor": "^7.18.9",
-        "@babel/helper-module-imports": "^7.18.6",
-        "@babel/helper-simple-access": "^7.19.4",
-        "@babel/helper-split-export-declaration": "^7.18.6",
-        "@babel/helper-validator-identifier": "^7.19.1",
-        "@babel/template": "^7.18.10",
-        "@babel/traverse": "^7.19.6",
-        "@babel/types": "^7.19.4"
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-module-imports": "^7.22.15",
+        "@babel/helper-simple-access": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/helper-validator-identifier": "^7.22.20"
       },
       "engines": {
         "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
       }
     },
     "node_modules/@babel/helper-optimise-call-expression": {
@@ -378,12 +393,12 @@
       }
     },
     "node_modules/@babel/helper-simple-access": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz",
-      "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+      "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.19.4"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -402,39 +417,39 @@
       }
     },
     "node_modules/@babel/helper-split-export-declaration": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
-      "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
-      "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
+      "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.19.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
-      "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+      "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-option": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
-      "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
+      "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
@@ -456,27 +471,27 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.4.tgz",
-      "integrity": "sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw==",
+      "version": "7.23.1",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz",
+      "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==",
       "dev": true,
       "dependencies": {
-        "@babel/template": "^7.18.10",
-        "@babel/traverse": "^7.19.4",
-        "@babel/types": "^7.19.4"
+        "@babel/template": "^7.22.15",
+        "@babel/traverse": "^7.23.0",
+        "@babel/types": "^7.23.0"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/highlight": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
-      "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
+      "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-validator-identifier": "^7.18.6",
-        "chalk": "^2.0.0",
+        "@babel/helper-validator-identifier": "^7.22.20",
+        "chalk": "^2.4.2",
         "js-tokens": "^4.0.0"
       },
       "engines": {
@@ -484,9 +499,9 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz",
-      "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
+      "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
       "dev": true,
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -1741,33 +1756,33 @@
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.18.10",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
-      "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+      "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
       "dev": true,
       "dependencies": {
-        "@babel/code-frame": "^7.18.6",
-        "@babel/parser": "^7.18.10",
-        "@babel/types": "^7.18.10"
+        "@babel/code-frame": "^7.22.13",
+        "@babel/parser": "^7.22.15",
+        "@babel/types": "^7.22.15"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz",
-      "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==",
-      "dev": true,
-      "dependencies": {
-        "@babel/code-frame": "^7.18.6",
-        "@babel/generator": "^7.19.6",
-        "@babel/helper-environment-visitor": "^7.18.9",
-        "@babel/helper-function-name": "^7.19.0",
-        "@babel/helper-hoist-variables": "^7.18.6",
-        "@babel/helper-split-export-declaration": "^7.18.6",
-        "@babel/parser": "^7.19.6",
-        "@babel/types": "^7.19.4",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz",
+      "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.22.13",
+        "@babel/generator": "^7.23.0",
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-function-name": "^7.23.0",
+        "@babel/helper-hoist-variables": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/parser": "^7.23.0",
+        "@babel/types": "^7.23.0",
         "debug": "^4.1.0",
         "globals": "^11.1.0"
       },
@@ -1776,13 +1791,13 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz",
-      "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
+      "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-string-parser": "^7.19.4",
-        "@babel/helper-validator-identifier": "^7.19.1",
+        "@babel/helper-string-parser": "^7.22.5",
+        "@babel/helper-validator-identifier": "^7.22.20",
         "to-fast-properties": "^2.0.0"
       },
       "engines": {
@@ -2195,9 +2210,9 @@
       }
     },
     "node_modules/@jridgewell/source-map": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
-      "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
+      "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
       "dev": true,
       "dependencies": {
         "@jridgewell/gen-mapping": "^0.3.0",
@@ -2205,9 +2220,9 @@
       }
     },
     "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
-      "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
       "dev": true,
       "dependencies": {
         "@jridgewell/set-array": "^1.0.1",
@@ -2334,9 +2349,9 @@
       }
     },
     "node_modules/@types/estree": {
-      "version": "0.0.51",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
-      "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
+      "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
       "dev": true
     },
     "node_modules/@types/html-minifier-terser": {
@@ -2650,148 +2665,148 @@
       }
     },
     "node_modules/@webassemblyjs/ast": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
-      "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
+      "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/helper-numbers": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1"
+        "@webassemblyjs/helper-numbers": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
       }
     },
     "node_modules/@webassemblyjs/floating-point-hex-parser": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
-      "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
+      "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
       "dev": true
     },
     "node_modules/@webassemblyjs/helper-api-error": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
-      "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
+      "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
       "dev": true
     },
     "node_modules/@webassemblyjs/helper-buffer": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
-      "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
+      "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
       "dev": true
     },
     "node_modules/@webassemblyjs/helper-numbers": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
-      "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
+      "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/floating-point-hex-parser": "1.11.1",
-        "@webassemblyjs/helper-api-error": "1.11.1",
+        "@webassemblyjs/floating-point-hex-parser": "1.11.6",
+        "@webassemblyjs/helper-api-error": "1.11.6",
         "@xtuc/long": "4.2.2"
       }
     },
     "node_modules/@webassemblyjs/helper-wasm-bytecode": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
-      "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
+      "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
       "dev": true
     },
     "node_modules/@webassemblyjs/helper-wasm-section": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
-      "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
+      "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-buffer": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/wasm-gen": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/wasm-gen": "1.11.6"
       }
     },
     "node_modules/@webassemblyjs/ieee754": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
-      "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
+      "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
       "dev": true,
       "dependencies": {
         "@xtuc/ieee754": "^1.2.0"
       }
     },
     "node_modules/@webassemblyjs/leb128": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
-      "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
+      "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
       "dev": true,
       "dependencies": {
         "@xtuc/long": "4.2.2"
       }
     },
     "node_modules/@webassemblyjs/utf8": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
-      "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
+      "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
       "dev": true
     },
     "node_modules/@webassemblyjs/wasm-edit": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
-      "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
+      "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-buffer": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/helper-wasm-section": "1.11.1",
-        "@webassemblyjs/wasm-gen": "1.11.1",
-        "@webassemblyjs/wasm-opt": "1.11.1",
-        "@webassemblyjs/wasm-parser": "1.11.1",
-        "@webassemblyjs/wast-printer": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/helper-wasm-section": "1.11.6",
+        "@webassemblyjs/wasm-gen": "1.11.6",
+        "@webassemblyjs/wasm-opt": "1.11.6",
+        "@webassemblyjs/wasm-parser": "1.11.6",
+        "@webassemblyjs/wast-printer": "1.11.6"
       }
     },
     "node_modules/@webassemblyjs/wasm-gen": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
-      "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
+      "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/ieee754": "1.11.1",
-        "@webassemblyjs/leb128": "1.11.1",
-        "@webassemblyjs/utf8": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/ieee754": "1.11.6",
+        "@webassemblyjs/leb128": "1.11.6",
+        "@webassemblyjs/utf8": "1.11.6"
       }
     },
     "node_modules/@webassemblyjs/wasm-opt": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
-      "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
+      "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-buffer": "1.11.1",
-        "@webassemblyjs/wasm-gen": "1.11.1",
-        "@webassemblyjs/wasm-parser": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/wasm-gen": "1.11.6",
+        "@webassemblyjs/wasm-parser": "1.11.6"
       }
     },
     "node_modules/@webassemblyjs/wasm-parser": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
-      "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
+      "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-api-error": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/ieee754": "1.11.1",
-        "@webassemblyjs/leb128": "1.11.1",
-        "@webassemblyjs/utf8": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-api-error": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/ieee754": "1.11.6",
+        "@webassemblyjs/leb128": "1.11.6",
+        "@webassemblyjs/utf8": "1.11.6"
       }
     },
     "node_modules/@webassemblyjs/wast-printer": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
-      "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
+      "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
       "dev": true,
       "dependencies": {
-        "@webassemblyjs/ast": "1.11.1",
+        "@webassemblyjs/ast": "1.11.6",
         "@xtuc/long": "4.2.2"
       }
     },
@@ -2844,9 +2859,9 @@
       "dev": true
     },
     "node_modules/acorn": {
-      "version": "8.8.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
-      "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+      "version": "8.10.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+      "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
       "dev": true,
       "bin": {
         "acorn": "bin/acorn"
@@ -2856,9 +2871,9 @@
       }
     },
     "node_modules/acorn-import-assertions": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
-      "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+      "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
       "dev": true,
       "peerDependencies": {
         "acorn": "^8"
@@ -3201,9 +3216,9 @@
       }
     },
     "node_modules/browserslist": {
-      "version": "4.21.4",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
-      "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
+      "version": "4.22.0",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.0.tgz",
+      "integrity": "sha512-v+Jcv64L2LbfTC6OnRcaxtqJNJuQAVhZKSJfR/6hn7lhnChUXl4amwVviqN1k411BB+3rRoKMitELRn1CojeRA==",
       "dev": true,
       "funding": [
         {
@@ -3213,13 +3228,17 @@
         {
           "type": "tidelift",
           "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
         }
       ],
       "dependencies": {
-        "caniuse-lite": "^1.0.30001400",
-        "electron-to-chromium": "^1.4.251",
-        "node-releases": "^2.0.6",
-        "update-browserslist-db": "^1.0.9"
+        "caniuse-lite": "^1.0.30001539",
+        "electron-to-chromium": "^1.4.530",
+        "node-releases": "^2.0.13",
+        "update-browserslist-db": "^1.0.13"
       },
       "bin": {
         "browserslist": "cli.js"
@@ -3281,9 +3300,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001423",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz",
-      "integrity": "sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ==",
+      "version": "1.0.30001539",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
+      "integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==",
       "dev": true,
       "funding": [
         {
@@ -3293,6 +3312,10 @@
         {
           "type": "tidelift",
           "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
         }
       ]
     },
@@ -3434,9 +3457,9 @@
       "dev": true
     },
     "node_modules/convert-source-map": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
-      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
       "dev": true
     },
     "node_modules/core-js-compat": {
@@ -3865,9 +3888,9 @@
       "dev": true
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.284",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
-      "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
+      "version": "1.4.530",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.530.tgz",
+      "integrity": "sha512-rsJ9O8SCI4etS8TBsXuRfHa2eZReJhnGf5MHZd3Vo05PukWHKXhk3VQGbHHnDLa8nZz9woPCpLCMQpLGgkGNRA==",
       "dev": true
     },
     "node_modules/emojis-list": {
@@ -3889,9 +3912,9 @@
       }
     },
     "node_modules/enhanced-resolve": {
-      "version": "5.10.0",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
-      "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
+      "version": "5.15.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+      "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
       "dev": true,
       "dependencies": {
         "graceful-fs": "^4.2.4",
@@ -3970,9 +3993,9 @@
       }
     },
     "node_modules/es-module-lexer": {
-      "version": "0.9.3",
-      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
-      "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz",
+      "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==",
       "dev": true
     },
     "node_modules/es-shim-unscopables": {
@@ -5834,9 +5857,9 @@
       "peer": true
     },
     "node_modules/json5": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
-      "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
       "dev": true,
       "bin": {
         "json5": "lib/cli.js"
@@ -6195,9 +6218,9 @@
       "dev": true
     },
     "node_modules/node-releases": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
-      "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
+      "version": "2.0.13",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
+      "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
       "dev": true
     },
     "node_modules/normalize-path": {
@@ -8110,18 +8133,18 @@
       }
     },
     "node_modules/semver": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
       "dev": true,
       "bin": {
         "semver": "bin/semver.js"
       }
     },
     "node_modules/serialize-javascript": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
-      "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
+      "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
       "dev": true,
       "dependencies": {
         "randombytes": "^2.1.0"
@@ -8418,13 +8441,13 @@
       }
     },
     "node_modules/terser": {
-      "version": "5.15.1",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz",
-      "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==",
+      "version": "5.20.0",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz",
+      "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==",
       "dev": true,
       "dependencies": {
-        "@jridgewell/source-map": "^0.3.2",
-        "acorn": "^8.5.0",
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.8.2",
         "commander": "^2.20.0",
         "source-map-support": "~0.5.20"
       },
@@ -8436,16 +8459,16 @@
       }
     },
     "node_modules/terser-webpack-plugin": {
-      "version": "5.3.6",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz",
-      "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==",
+      "version": "5.3.9",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
+      "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
       "dev": true,
       "dependencies": {
-        "@jridgewell/trace-mapping": "^0.3.14",
+        "@jridgewell/trace-mapping": "^0.3.17",
         "jest-worker": "^27.4.5",
         "schema-utils": "^3.1.1",
-        "serialize-javascript": "^6.0.0",
-        "terser": "^5.14.1"
+        "serialize-javascript": "^6.0.1",
+        "terser": "^5.16.8"
       },
       "engines": {
         "node": ">= 10.13.0"
@@ -8470,9 +8493,9 @@
       }
     },
     "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-      "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+      "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.8",
@@ -8881,9 +8904,9 @@
       }
     },
     "node_modules/update-browserslist-db": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
-      "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+      "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
       "dev": true,
       "funding": [
         {
@@ -8893,6 +8916,10 @@
         {
           "type": "tidelift",
           "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
         }
       ],
       "dependencies": {
@@ -8900,7 +8927,7 @@
         "picocolors": "^1.0.0"
       },
       "bin": {
-        "browserslist-lint": "cli.js"
+        "update-browserslist-db": "cli.js"
       },
       "peerDependencies": {
         "browserslist": ">= 4.21.0"
@@ -8961,22 +8988,22 @@
       }
     },
     "node_modules/webpack": {
-      "version": "5.74.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
-      "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
+      "version": "5.88.2",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
+      "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
       "dev": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.3",
-        "@types/estree": "^0.0.51",
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/wasm-edit": "1.11.1",
-        "@webassemblyjs/wasm-parser": "1.11.1",
+        "@types/estree": "^1.0.0",
+        "@webassemblyjs/ast": "^1.11.5",
+        "@webassemblyjs/wasm-edit": "^1.11.5",
+        "@webassemblyjs/wasm-parser": "^1.11.5",
         "acorn": "^8.7.1",
-        "acorn-import-assertions": "^1.7.6",
+        "acorn-import-assertions": "^1.9.0",
         "browserslist": "^4.14.5",
         "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.10.0",
-        "es-module-lexer": "^0.9.0",
+        "enhanced-resolve": "^5.15.0",
+        "es-module-lexer": "^1.2.1",
         "eslint-scope": "5.1.1",
         "events": "^3.2.0",
         "glob-to-regexp": "^0.4.1",
@@ -8985,9 +9012,9 @@
         "loader-runner": "^4.2.0",
         "mime-types": "^2.1.27",
         "neo-async": "^2.6.2",
-        "schema-utils": "^3.1.0",
+        "schema-utils": "^3.2.0",
         "tapable": "^2.1.1",
-        "terser-webpack-plugin": "^5.1.3",
+        "terser-webpack-plugin": "^5.3.7",
         "watchpack": "^2.4.0",
         "webpack-sources": "^3.2.3"
       },
@@ -9086,9 +9113,9 @@
       }
     },
     "node_modules/webpack/node_modules/schema-utils": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-      "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+      "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.8",
@@ -9227,51 +9254,53 @@
       }
     },
     "@babel/code-frame": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
-      "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+      "version": "7.22.13",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+      "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
       "dev": true,
       "requires": {
-        "@babel/highlight": "^7.18.6"
+        "@babel/highlight": "^7.22.13",
+        "chalk": "^2.4.2"
       }
     },
     "@babel/compat-data": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.4.tgz",
-      "integrity": "sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz",
+      "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==",
       "dev": true
     },
     "@babel/core": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz",
-      "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==",
-      "dev": true,
-      "requires": {
-        "@ampproject/remapping": "^2.1.0",
-        "@babel/code-frame": "^7.18.6",
-        "@babel/generator": "^7.19.6",
-        "@babel/helper-compilation-targets": "^7.19.3",
-        "@babel/helper-module-transforms": "^7.19.6",
-        "@babel/helpers": "^7.19.4",
-        "@babel/parser": "^7.19.6",
-        "@babel/template": "^7.18.10",
-        "@babel/traverse": "^7.19.6",
-        "@babel/types": "^7.19.4",
-        "convert-source-map": "^1.7.0",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz",
+      "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==",
+      "dev": true,
+      "requires": {
+        "@ampproject/remapping": "^2.2.0",
+        "@babel/code-frame": "^7.22.13",
+        "@babel/generator": "^7.23.0",
+        "@babel/helper-compilation-targets": "^7.22.15",
+        "@babel/helper-module-transforms": "^7.23.0",
+        "@babel/helpers": "^7.23.0",
+        "@babel/parser": "^7.23.0",
+        "@babel/template": "^7.22.15",
+        "@babel/traverse": "^7.23.0",
+        "@babel/types": "^7.23.0",
+        "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
-        "json5": "^2.2.1",
-        "semver": "^6.3.0"
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
       }
     },
     "@babel/generator": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.6.tgz",
-      "integrity": "sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
+      "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.19.4",
+        "@babel/types": "^7.23.0",
         "@jridgewell/gen-mapping": "^0.3.2",
+        "@jridgewell/trace-mapping": "^0.3.17",
         "jsesc": "^2.5.1"
       },
       "dependencies": {
@@ -9308,15 +9337,33 @@
       }
     },
     "@babel/helper-compilation-targets": {
-      "version": "7.19.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz",
-      "integrity": "sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
+      "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
       "dev": true,
       "requires": {
-        "@babel/compat-data": "^7.19.3",
-        "@babel/helper-validator-option": "^7.18.6",
-        "browserslist": "^4.21.3",
-        "semver": "^6.3.0"
+        "@babel/compat-data": "^7.22.9",
+        "@babel/helper-validator-option": "^7.22.15",
+        "browserslist": "^4.21.9",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
       }
     },
     "@babel/helper-create-class-features-plugin": {
@@ -9359,9 +9406,9 @@
       }
     },
     "@babel/helper-environment-visitor": {
-      "version": "7.18.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
-      "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
+      "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
       "dev": true
     },
     "@babel/helper-explode-assignable-expression": {
@@ -9374,22 +9421,22 @@
       }
     },
     "@babel/helper-function-name": {
-      "version": "7.19.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
-      "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
+      "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
       "dev": true,
       "requires": {
-        "@babel/template": "^7.18.10",
-        "@babel/types": "^7.19.0"
+        "@babel/template": "^7.22.15",
+        "@babel/types": "^7.23.0"
       }
     },
     "@babel/helper-hoist-variables": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
-      "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+      "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       }
     },
     "@babel/helper-member-expression-to-functions": {
@@ -9402,28 +9449,25 @@
       }
     },
     "@babel/helper-module-imports": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
-      "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
+      "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.15"
       }
     },
     "@babel/helper-module-transforms": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz",
-      "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
+      "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
       "dev": true,
       "requires": {
-        "@babel/helper-environment-visitor": "^7.18.9",
-        "@babel/helper-module-imports": "^7.18.6",
-        "@babel/helper-simple-access": "^7.19.4",
-        "@babel/helper-split-export-declaration": "^7.18.6",
-        "@babel/helper-validator-identifier": "^7.19.1",
-        "@babel/template": "^7.18.10",
-        "@babel/traverse": "^7.19.6",
-        "@babel/types": "^7.19.4"
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-module-imports": "^7.22.15",
+        "@babel/helper-simple-access": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/helper-validator-identifier": "^7.22.20"
       }
     },
     "@babel/helper-optimise-call-expression": {
@@ -9467,12 +9511,12 @@
       }
     },
     "@babel/helper-simple-access": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz",
-      "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+      "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.19.4"
+        "@babel/types": "^7.22.5"
       }
     },
     "@babel/helper-skip-transparent-expression-wrappers": {
@@ -9485,30 +9529,30 @@
       }
     },
     "@babel/helper-split-export-declaration": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
-      "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       }
     },
     "@babel/helper-string-parser": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
-      "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
+      "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
       "dev": true
     },
     "@babel/helper-validator-identifier": {
-      "version": "7.19.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
-      "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+      "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
       "dev": true
     },
     "@babel/helper-validator-option": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
-      "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
+      "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
       "dev": true
     },
     "@babel/helper-wrap-function": {
@@ -9524,31 +9568,31 @@
       }
     },
     "@babel/helpers": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.4.tgz",
-      "integrity": "sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw==",
+      "version": "7.23.1",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz",
+      "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==",
       "dev": true,
       "requires": {
-        "@babel/template": "^7.18.10",
-        "@babel/traverse": "^7.19.4",
-        "@babel/types": "^7.19.4"
+        "@babel/template": "^7.22.15",
+        "@babel/traverse": "^7.23.0",
+        "@babel/types": "^7.23.0"
       }
     },
     "@babel/highlight": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
-      "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+      "version": "7.22.20",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
+      "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
       "dev": true,
       "requires": {
-        "@babel/helper-validator-identifier": "^7.18.6",
-        "chalk": "^2.0.0",
+        "@babel/helper-validator-identifier": "^7.22.20",
+        "chalk": "^2.4.2",
         "js-tokens": "^4.0.0"
       }
     },
     "@babel/parser": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz",
-      "integrity": "sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
+      "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
       "dev": true
     },
     "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
@@ -10380,42 +10424,42 @@
       }
     },
     "@babel/template": {
-      "version": "7.18.10",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
-      "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
+      "version": "7.22.15",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+      "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
       "dev": true,
       "requires": {
-        "@babel/code-frame": "^7.18.6",
-        "@babel/parser": "^7.18.10",
-        "@babel/types": "^7.18.10"
+        "@babel/code-frame": "^7.22.13",
+        "@babel/parser": "^7.22.15",
+        "@babel/types": "^7.22.15"
       }
     },
     "@babel/traverse": {
-      "version": "7.19.6",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.6.tgz",
-      "integrity": "sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==",
-      "dev": true,
-      "requires": {
-        "@babel/code-frame": "^7.18.6",
-        "@babel/generator": "^7.19.6",
-        "@babel/helper-environment-visitor": "^7.18.9",
-        "@babel/helper-function-name": "^7.19.0",
-        "@babel/helper-hoist-variables": "^7.18.6",
-        "@babel/helper-split-export-declaration": "^7.18.6",
-        "@babel/parser": "^7.19.6",
-        "@babel/types": "^7.19.4",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz",
+      "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.22.13",
+        "@babel/generator": "^7.23.0",
+        "@babel/helper-environment-visitor": "^7.22.20",
+        "@babel/helper-function-name": "^7.23.0",
+        "@babel/helper-hoist-variables": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/parser": "^7.23.0",
+        "@babel/types": "^7.23.0",
         "debug": "^4.1.0",
         "globals": "^11.1.0"
       }
     },
     "@babel/types": {
-      "version": "7.19.4",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz",
-      "integrity": "sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==",
+      "version": "7.23.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
+      "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
       "dev": true,
       "requires": {
-        "@babel/helper-string-parser": "^7.19.4",
-        "@babel/helper-validator-identifier": "^7.19.1",
+        "@babel/helper-string-parser": "^7.22.5",
+        "@babel/helper-validator-identifier": "^7.22.20",
         "to-fast-properties": "^2.0.0"
       }
     },
@@ -10646,9 +10690,9 @@
       "dev": true
     },
     "@jridgewell/source-map": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
-      "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
+      "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
       "dev": true,
       "requires": {
         "@jridgewell/gen-mapping": "^0.3.0",
@@ -10656,9 +10700,9 @@
       },
       "dependencies": {
         "@jridgewell/gen-mapping": {
-          "version": "0.3.2",
-          "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
-          "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+          "version": "0.3.3",
+          "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+          "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
           "dev": true,
           "requires": {
             "@jridgewell/set-array": "^1.0.1",
@@ -10765,9 +10809,9 @@
       }
     },
     "@types/estree": {
-      "version": "0.0.51",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
-      "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
+      "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
       "dev": true
     },
     "@types/html-minifier-terser": {
@@ -10980,148 +11024,148 @@
       }
     },
     "@webassemblyjs/ast": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
-      "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
+      "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/helper-numbers": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1"
+        "@webassemblyjs/helper-numbers": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6"
       }
     },
     "@webassemblyjs/floating-point-hex-parser": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
-      "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
+      "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
       "dev": true
     },
     "@webassemblyjs/helper-api-error": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
-      "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
+      "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
       "dev": true
     },
     "@webassemblyjs/helper-buffer": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
-      "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
+      "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==",
       "dev": true
     },
     "@webassemblyjs/helper-numbers": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
-      "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
+      "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/floating-point-hex-parser": "1.11.1",
-        "@webassemblyjs/helper-api-error": "1.11.1",
+        "@webassemblyjs/floating-point-hex-parser": "1.11.6",
+        "@webassemblyjs/helper-api-error": "1.11.6",
         "@xtuc/long": "4.2.2"
       }
     },
     "@webassemblyjs/helper-wasm-bytecode": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
-      "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
+      "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
       "dev": true
     },
     "@webassemblyjs/helper-wasm-section": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
-      "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
+      "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-buffer": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/wasm-gen": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/wasm-gen": "1.11.6"
       }
     },
     "@webassemblyjs/ieee754": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
-      "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
+      "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
       "dev": true,
       "requires": {
         "@xtuc/ieee754": "^1.2.0"
       }
     },
     "@webassemblyjs/leb128": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
-      "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
+      "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
       "dev": true,
       "requires": {
         "@xtuc/long": "4.2.2"
       }
     },
     "@webassemblyjs/utf8": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
-      "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
+      "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
       "dev": true
     },
     "@webassemblyjs/wasm-edit": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
-      "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
+      "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-buffer": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/helper-wasm-section": "1.11.1",
-        "@webassemblyjs/wasm-gen": "1.11.1",
-        "@webassemblyjs/wasm-opt": "1.11.1",
-        "@webassemblyjs/wasm-parser": "1.11.1",
-        "@webassemblyjs/wast-printer": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/helper-wasm-section": "1.11.6",
+        "@webassemblyjs/wasm-gen": "1.11.6",
+        "@webassemblyjs/wasm-opt": "1.11.6",
+        "@webassemblyjs/wasm-parser": "1.11.6",
+        "@webassemblyjs/wast-printer": "1.11.6"
       }
     },
     "@webassemblyjs/wasm-gen": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
-      "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
+      "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/ieee754": "1.11.1",
-        "@webassemblyjs/leb128": "1.11.1",
-        "@webassemblyjs/utf8": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/ieee754": "1.11.6",
+        "@webassemblyjs/leb128": "1.11.6",
+        "@webassemblyjs/utf8": "1.11.6"
       }
     },
     "@webassemblyjs/wasm-opt": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
-      "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
+      "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-buffer": "1.11.1",
-        "@webassemblyjs/wasm-gen": "1.11.1",
-        "@webassemblyjs/wasm-parser": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-buffer": "1.11.6",
+        "@webassemblyjs/wasm-gen": "1.11.6",
+        "@webassemblyjs/wasm-parser": "1.11.6"
       }
     },
     "@webassemblyjs/wasm-parser": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
-      "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
+      "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/helper-api-error": "1.11.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.11.1",
-        "@webassemblyjs/ieee754": "1.11.1",
-        "@webassemblyjs/leb128": "1.11.1",
-        "@webassemblyjs/utf8": "1.11.1"
+        "@webassemblyjs/ast": "1.11.6",
+        "@webassemblyjs/helper-api-error": "1.11.6",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.6",
+        "@webassemblyjs/ieee754": "1.11.6",
+        "@webassemblyjs/leb128": "1.11.6",
+        "@webassemblyjs/utf8": "1.11.6"
       }
     },
     "@webassemblyjs/wast-printer": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
-      "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
+      "version": "1.11.6",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
+      "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.11.1",
+        "@webassemblyjs/ast": "1.11.6",
         "@xtuc/long": "4.2.2"
       }
     },
@@ -11161,15 +11205,15 @@
       "dev": true
     },
     "acorn": {
-      "version": "8.8.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
-      "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+      "version": "8.10.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+      "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
       "dev": true
     },
     "acorn-import-assertions": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
-      "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+      "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
       "dev": true,
       "requires": {}
     },
@@ -11415,15 +11459,15 @@
       }
     },
     "browserslist": {
-      "version": "4.21.4",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
-      "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
+      "version": "4.22.0",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.0.tgz",
+      "integrity": "sha512-v+Jcv64L2LbfTC6OnRcaxtqJNJuQAVhZKSJfR/6hn7lhnChUXl4amwVviqN1k411BB+3rRoKMitELRn1CojeRA==",
       "dev": true,
       "requires": {
-        "caniuse-lite": "^1.0.30001400",
-        "electron-to-chromium": "^1.4.251",
-        "node-releases": "^2.0.6",
-        "update-browserslist-db": "^1.0.9"
+        "caniuse-lite": "^1.0.30001539",
+        "electron-to-chromium": "^1.4.530",
+        "node-releases": "^2.0.13",
+        "update-browserslist-db": "^1.0.13"
       }
     },
     "buffer-from": {
@@ -11472,9 +11516,9 @@
       "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
     },
     "caniuse-lite": {
-      "version": "1.0.30001423",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001423.tgz",
-      "integrity": "sha512-09iwWGOlifvE1XuHokFMP7eR38a0JnajoyL3/i87c8ZjRWRrdKo1fqjNfugfBD0UDBIOz0U+jtNhJ0EPm1VleQ==",
+      "version": "1.0.30001539",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
+      "integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==",
       "dev": true
     },
     "chalk": {
@@ -11585,9 +11629,9 @@
       "dev": true
     },
     "convert-source-map": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
-      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
       "dev": true
     },
     "core-js-compat": {
@@ -11891,9 +11935,9 @@
       "dev": true
     },
     "electron-to-chromium": {
-      "version": "1.4.284",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
-      "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
+      "version": "1.4.530",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.530.tgz",
+      "integrity": "sha512-rsJ9O8SCI4etS8TBsXuRfHa2eZReJhnGf5MHZd3Vo05PukWHKXhk3VQGbHHnDLa8nZz9woPCpLCMQpLGgkGNRA==",
       "dev": true
     },
     "emojis-list": {
@@ -11912,9 +11956,9 @@
       }
     },
     "enhanced-resolve": {
-      "version": "5.10.0",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
-      "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
+      "version": "5.15.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+      "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
       "dev": true,
       "requires": {
         "graceful-fs": "^4.2.4",
@@ -11975,9 +12019,9 @@
       }
     },
     "es-module-lexer": {
-      "version": "0.9.3",
-      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
-      "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz",
+      "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==",
       "dev": true
     },
     "es-shim-unscopables": {
@@ -13313,9 +13357,9 @@
       "peer": true
     },
     "json5": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
-      "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
       "dev": true
     },
     "jsonfile": {
@@ -13591,9 +13635,9 @@
       }
     },
     "node-releases": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
-      "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
+      "version": "2.0.13",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
+      "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
       "dev": true
     },
     "normalize-path": {
@@ -14844,15 +14888,15 @@
       }
     },
     "semver": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
       "dev": true
     },
     "serialize-javascript": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
-      "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
+      "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
       "dev": true,
       "requires": {
         "randombytes": "^2.1.0"
@@ -15071,13 +15115,13 @@
       "dev": true
     },
     "terser": {
-      "version": "5.15.1",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz",
-      "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==",
+      "version": "5.20.0",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz",
+      "integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==",
       "dev": true,
       "requires": {
-        "@jridgewell/source-map": "^0.3.2",
-        "acorn": "^8.5.0",
+        "@jridgewell/source-map": "^0.3.3",
+        "acorn": "^8.8.2",
         "commander": "^2.20.0",
         "source-map-support": "~0.5.20"
       },
@@ -15091,22 +15135,22 @@
       }
     },
     "terser-webpack-plugin": {
-      "version": "5.3.6",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz",
-      "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==",
+      "version": "5.3.9",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz",
+      "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==",
       "dev": true,
       "requires": {
-        "@jridgewell/trace-mapping": "^0.3.14",
+        "@jridgewell/trace-mapping": "^0.3.17",
         "jest-worker": "^27.4.5",
         "schema-utils": "^3.1.1",
-        "serialize-javascript": "^6.0.0",
-        "terser": "^5.14.1"
+        "serialize-javascript": "^6.0.1",
+        "terser": "^5.16.8"
       },
       "dependencies": {
         "schema-utils": {
-          "version": "3.1.1",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-          "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+          "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.8",
@@ -15403,9 +15447,9 @@
       "dev": true
     },
     "update-browserslist-db": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
-      "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+      "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
       "dev": true,
       "requires": {
         "escalade": "^3.1.1",
@@ -15458,22 +15502,22 @@
       }
     },
     "webpack": {
-      "version": "5.74.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
-      "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
+      "version": "5.88.2",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
+      "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
       "dev": true,
       "requires": {
         "@types/eslint-scope": "^3.7.3",
-        "@types/estree": "^0.0.51",
-        "@webassemblyjs/ast": "1.11.1",
-        "@webassemblyjs/wasm-edit": "1.11.1",
-        "@webassemblyjs/wasm-parser": "1.11.1",
+        "@types/estree": "^1.0.0",
+        "@webassemblyjs/ast": "^1.11.5",
+        "@webassemblyjs/wasm-edit": "^1.11.5",
+        "@webassemblyjs/wasm-parser": "^1.11.5",
         "acorn": "^8.7.1",
-        "acorn-import-assertions": "^1.7.6",
+        "acorn-import-assertions": "^1.9.0",
         "browserslist": "^4.14.5",
         "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.10.0",
-        "es-module-lexer": "^0.9.0",
+        "enhanced-resolve": "^5.15.0",
+        "es-module-lexer": "^1.2.1",
         "eslint-scope": "5.1.1",
         "events": "^3.2.0",
         "glob-to-regexp": "^0.4.1",
@@ -15482,17 +15526,17 @@
         "loader-runner": "^4.2.0",
         "mime-types": "^2.1.27",
         "neo-async": "^2.6.2",
-        "schema-utils": "^3.1.0",
+        "schema-utils": "^3.2.0",
         "tapable": "^2.1.1",
-        "terser-webpack-plugin": "^5.1.3",
+        "terser-webpack-plugin": "^5.3.7",
         "watchpack": "^2.4.0",
         "webpack-sources": "^3.2.3"
       },
       "dependencies": {
         "schema-utils": {
-          "version": "3.1.1",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
-          "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+          "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
           "dev": true,
           "requires": {
             "@types/json-schema": "^7.0.8",
diff --git a/templates/package.json b/templates/package.json
index b27405e7..c3ec7e4a 100644
--- a/templates/package.json
+++ b/templates/package.json
@@ -14,7 +14,7 @@
     "tailwindcss": "^3.2.1"
   },
   "devDependencies": {
-    "@babel/core": "^7.19.6",
+    "@babel/core": "^7.23.0",
     "@babel/preset-env": "^7.19.4",
     "@babel/preset-react": "^7.18.6",
     "@babel/preset-typescript": "^7.18.6",
@@ -40,7 +40,7 @@
     "style-loader": "^3.3.1",
     "ts-loader": "^9.4.1",
     "typescript": "^4.8.4",
-    "webpack": "^5.74.0",
+    "webpack": "^5.76.0",
     "webpack-cli": "^4.10.0"
   },
   "homepage": "https://flama.dev",


Test patch:
diff --git a/tests/ddd/__init__.py b/tests/ddd/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/ddd/test_components.py b/tests/ddd/test_components.py
new file mode 100644
index 00000000..341233c3
--- /dev/null
+++ b/tests/ddd/test_components.py
@@ -0,0 +1,74 @@
+import pytest
+
+from flama import types
+from flama.ddd import AbstractRepository, AbstractWorker
+from flama.ddd.components import WorkerComponent
+from flama.injection.resolver import Parameter
+
+
+class TestCaseWorkerComponent:
+    @pytest.fixture(scope="function")
+    def repository(self):
+        class Repository(AbstractRepository):
+            ...
+
+        return Repository
+
+    @pytest.fixture(scope="function")
+    def worker(self, client, repository):
+        class FooWorker(AbstractWorker):
+            async def __aenter__(self) -> "AbstractWorker":
+                return self
+
+            async def __aexit__(self, exc_type, exc_val, exc_tb):
+                pass
+
+            async def commit(self) -> None:
+                pass
+
+            async def rollback(self) -> None:
+                pass
+
+            bar: repository
+
+        return FooWorker()
+
+    @pytest.fixture(scope="function")
+    def component(self, worker):
+        return WorkerComponent(worker)
+
+    def test_init(self, component, worker):
+        assert component.worker == worker
+
+    @pytest.fixture(scope="function")
+    def parameter_types(self, worker):
+        return {
+            "abstract_worker": AbstractWorker,
+            "foo_worker": worker.__class__,
+            "int": int,
+        }
+
+    @pytest.mark.parametrize(
+        ["param_name", "param_type", "expected"],
+        [
+            pytest.param("foo", "foo_worker", True, id="handle"),
+            pytest.param("foo", "abstract_worker", False, id="not_handle_abstract"),
+            pytest.param("foo", "int", False, id="not_handle_int"),
+        ],
+    )
+    def test_can_handle_parameter(self, component, param_name, param_type, parameter_types, expected):
+        assert component.can_handle_parameter(Parameter(param_name, parameter_types[param_type])) == expected
+
+    def test_resolve(self, component, worker):
+        class FooApp:
+            ...
+
+        scopes = types.Scope({"app": FooApp()})
+
+        assert hasattr(component.worker, "_app")
+        assert not component.worker._app
+
+        component.resolve(scopes)
+        assert component.resolve(scopes) == worker
+        assert hasattr(component.worker, "_app")
+        assert component.worker._app == scopes["app"]
diff --git a/tests/ddd/test_repositories.py b/tests/ddd/test_repositories.py
new file mode 100644
index 00000000..5c5cafa4
--- /dev/null
+++ b/tests/ddd/test_repositories.py
@@ -0,0 +1,162 @@
+import pytest
+import sqlalchemy
+from sqlalchemy.ext.asyncio import AsyncConnection
+
+from flama import Flama
+from flama.ddd import SQLAlchemyRepository, exceptions
+from flama.sqlalchemy import SQLAlchemyModule
+from tests.utils import SQLAlchemyContext
+
+
+class TestCaseSQLAlchemyRepository:
+    @pytest.fixture(scope="function")
+    def app(self):
+        return Flama(schema=None, docs=None, modules={SQLAlchemyModule("sqlite+aiosqlite://")})
+
+    @pytest.fixture(scope="function")
+    async def connection(self, client):
+        # Exactly the same behavior than 'async with worker'
+        connection_: AsyncConnection = client.app.sqlalchemy.engine.connect()
+        await connection_.__aenter__()
+        transaction = connection_.begin()
+        await transaction.__aenter__()
+
+        yield connection_
+
+        await transaction.__aexit__(None, None, None)
+        await connection_.__aexit__(None, None, None)
+
+    @pytest.fixture(scope="function")
+    def tables(self, app):
+        return {
+            "single": sqlalchemy.Table(
+                "repository_table_single_pk",
+                app.sqlalchemy.metadata,
+                sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True, autoincrement=True),
+                sqlalchemy.Column("name", sqlalchemy.String, nullable=False),
+            ),
+            "composed": sqlalchemy.Table(
+                "repository_table_composed_pk",
+                app.sqlalchemy.metadata,
+                sqlalchemy.Column("id_first", sqlalchemy.Integer, primary_key=True),
+                sqlalchemy.Column("id_second", sqlalchemy.Integer, primary_key=True),
+                sqlalchemy.Column("name", sqlalchemy.String, nullable=False),
+            ),
+        }
+
+    @pytest.fixture(scope="function")
+    async def table(self, client, tables):
+        table = tables["single"]
+
+        async with SQLAlchemyContext(client.app, [table]):
+            yield table
+
+    @pytest.fixture(scope="function")
+    def repository(self, table, connection):
+        class Repository(SQLAlchemyRepository):
+            _table = table
+
+        return Repository(connection)
+
+    @pytest.fixture(scope="function")
+    async def repository_select(self, request, client, tables, connection):
+        table = tables[request.param]
+
+        async with SQLAlchemyContext(client.app, [table]):
+
+            class Repository(SQLAlchemyRepository):
+                _table = tables[request.param]
+
+            yield Repository(connection)
+
+    @pytest.mark.parametrize(
+        ["repository_select", "result", "exception"],
+        (
+            pytest.param("single", "id", None, id="single_pk"),
+            pytest.param(
+                "composed", None, exceptions.IntegrityError("Composed primary keys are not supported"), id="composed_pk"
+            ),
+        ),
+        indirect=["repository_select", "exception"],
+    )
+    async def test_primary_key(self, repository_select, result, exception):
+        with exception:
+            assert repository_select.primary_key.name == result
+
+    @pytest.mark.parametrize(
+        ["data", "result", "exception"],
+        (
+            pytest.param({"name": "foo"}, (1,), None, id="ok"),
+            pytest.param({"name": None}, None, exceptions.IntegrityError, id="integrity_error"),
+        ),
+        indirect=["exception"],
+    )
+    async def test_create(self, repository, data, result, exception):
+        with exception:
+            assert await repository.create(data) == result
+
+    @pytest.mark.parametrize(
+        ["data", "result", "exception"],
+        (
+            pytest.param(1, {"id": 1, "name": "foo"}, None, id="ok"),
+            pytest.param(2, None, exceptions.NotFoundError(1), id="not_found"),
+        ),
+        indirect=["exception"],
+    )
+    async def test_retrieve(self, data, result, exception, repository):
+        await repository.create({"name": "foo"})
+
+        with exception:
+            assert await repository.retrieve(data) == result
+
+    @pytest.mark.parametrize(
+        ["data", "result", "exception"],
+        (
+            pytest.param((1, {"name": "bar"}), {"id": 1, "name": "foo"}, None, id="ok"),
+            pytest.param((2, {"name": "bar"}), None, exceptions.NotFoundError(1), id="not_found"),
+        ),
+        indirect=["exception"],
+    )
+    async def test_update(self, data, result, exception, repository):
+        id_, data_ = data
+        await repository.create(data_)
+
+        with exception:
+            assert await repository.update(id_, {"name": "foo"}) == result
+
+    @pytest.mark.parametrize(
+        ["data", "result", "exception"],
+        (
+            pytest.param(1, {"id": 1, "name": "foo"}, None, id="ok"),
+            pytest.param(2, None, exceptions.NotFoundError(1), id="not_found"),
+        ),
+        indirect=["exception"],
+    )
+    async def test_delete(self, data, result, exception, repository):
+        await repository.create({"name": "foo"})
+
+        with exception:
+            await repository.delete(data)
+
+    @pytest.mark.parametrize(
+        ["clauses", "filters", "result"],
+        (
+            pytest.param([], {}, [{"id": 1, "name": "foo"}, {"id": 2, "name": "bar"}], id="all"),
+            pytest.param([lambda x: x.ilike("fo%")], {}, [{"id": 1, "name": "foo"}], id="clauses"),
+            pytest.param([], {"name": "foo"}, [{"id": 1, "name": "foo"}], id="filters"),
+        ),
+    )
+    async def test_list(self, clauses, filters, result, repository):
+        await repository.create({"name": "foo"})
+        await repository.create({"name": "bar"})
+
+        r = await repository.list(*[c(repository._table.c["name"]) for c in clauses], **filters)
+
+        assert r == result
+
+    async def test_drop(self, repository):
+        await repository.create({"name": "foo"})
+
+        result = await repository.drop()
+
+        assert result == 1
diff --git a/tests/ddd/test_workers.py b/tests/ddd/test_workers.py
new file mode 100644
index 00000000..2ab81b3e
--- /dev/null
+++ b/tests/ddd/test_workers.py
@@ -0,0 +1,143 @@
+from unittest.mock import AsyncMock, MagicMock, call, patch
+
+import pytest
+from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, AsyncTransaction
+
+from flama import Flama
+from flama.ddd import AbstractWorker, SQLAlchemyRepository, SQLAlchemyWorker, WorkerType
+from flama.ddd.repositories import AbstractRepository
+from flama.sqlalchemy import SQLAlchemyModule
+
+
+class TestCaseSQLAlchemyWorker:
+    @pytest.fixture(scope="function")
+    def app(self):
+        return Flama(schema=None, docs=None, modules={SQLAlchemyModule("sqlite+aiosqlite://")})
+
+    @pytest.fixture(scope="function")
+    def worker(self, client):
+        class FooWorker(SQLAlchemyWorker):
+            bar: SQLAlchemyRepository
+
+        return FooWorker(client.app)
+
+    def test_init(self, app):
+        worker = SQLAlchemyWorker(app)
+
+        assert worker._app == app
+        assert not hasattr(worker, "_connection")
+
+    def test_app(self, app):
+        worker = SQLAlchemyWorker()
+
+        with pytest.raises(AssertionError, match="Worker not initialized"):
+            worker.app
+
+        worker.app = app
+
+        assert worker.app == app
+
+        del worker.app
+
+        with pytest.raises(AssertionError, match="Worker not initialized"):
+            worker.app
+
+    def test_connection(self, worker):
+        with pytest.raises(AttributeError, match="Connection not initialized"):
+            worker.connection
+
+    async def test_begin(self, worker):
+        transaction_mock = AsyncMock(spec=AsyncTransaction)
+        connection_mock = AsyncMock(spec=AsyncConnection)
+        connection_mock.begin = transaction_mock
+        engine_mock = MagicMock(spec=AsyncEngine)
+        engine_mock.connect.return_value = connection_mock
+
+        with patch.object(worker.app.sqlalchemy, "engine", new=engine_mock):
+            await worker.begin()
+            assert engine_mock.connect.call_args_list == [call()]
+            assert connection_mock.__aenter__.await_args_list == [call()]
+            assert connection_mock.begin.call_args_list == [call()]
+            assert connection_mock.begin.await_args_list == [call()]
+
+    async def test_close(self, worker):
+        assert not hasattr(worker, "_transaction")
+        assert not hasattr(worker, "_connection")
+
+        connection_mock = AsyncMock(spec=AsyncConnection)
+        transaction_mock = AsyncMock(spec=AsyncTransaction)
+        worker._connection = connection_mock
+        worker._transaction = transaction_mock
+
+        await worker.close()
+
+        assert transaction_mock.__aexit__.await_args_list == [call(None, None, None)]
+        assert connection_mock.__aexit__.await_args_list == [call(None, None, None)]
+        assert not hasattr(worker, "_transaction")
+        assert not hasattr(worker, "_connection")
+
+    async def test_async_context(self, worker):
+        assert not hasattr(worker, "_transaction")
+        assert not hasattr(worker, "_connection")
+        assert not hasattr(worker, "bar")
+
+        async with worker:
+            assert worker._connection
+            assert worker._transaction
+            assert worker._transaction.is_active
+            assert hasattr(worker, "bar")
+            assert isinstance(worker.bar, SQLAlchemyRepository)
+
+        assert not hasattr(worker, "_transaction")
+        assert not hasattr(worker, "_connection")
+        assert not hasattr(worker, "bar")
+
+    async def test_commit(self, worker):
+        commit_mock = AsyncMock()
+        connection_mock = AsyncMock(spec=AsyncConnection)
+        connection_mock.commit = commit_mock
+
+        with patch.object(worker, "_connection", new=connection_mock, create=True):
+            await worker.commit()
+            assert commit_mock.await_args_list == [call()]
+
+    async def test_rollback(self, worker):
+        rollback_mock = AsyncMock()
+        connection_mock = AsyncMock(spec=AsyncConnection)
+        connection_mock.rollback = rollback_mock
+
+        with patch.object(worker, "_connection", new=connection_mock, create=True):
+            await worker.rollback()
+            assert rollback_mock.await_args_list == [call()]
+
+
+class TestCaseWorkerType:
+    @pytest.fixture(scope="function")
+    def worker(self):
+        class Worker(AbstractWorker):
+            async def __aenter__(self) -> "Worker":
+                return self
+
+            async def __aexit__(self, exc_type, exc_val, exc_tb):
+                return None
+
+            async def commit(self) -> None:
+                ...
+
+            async def rollback(self) -> None:
+                ...
+
+        return Worker
+
+    @pytest.fixture(scope="function")
+    def repository(self):
+        class Repository(AbstractRepository):
+            ...
+
+        return Repository
+
+    def test_custom_worker(self, worker, repository):
+        class CustomWorker(worker, metaclass=WorkerType):
+            foo: repository
+
+        assert CustomWorker._repositories == {"foo": repository}
diff --git a/tests/resources/test_crud.py b/tests/resources/test_crud.py
index 10158e0d..777a500d 100644
--- a/tests/resources/test_crud.py
+++ b/tests/resources/test_crud.py
@@ -7,14 +7,14 @@
 import pytest
 import sqlalchemy
 import typesystem
+import typesystem.fields
 from sqlalchemy.dialects import postgresql
 
-from flama import types
 from flama.applications import Flama
-from flama.pagination import paginator
 from flama.resources.crud import CRUDListDropResourceType, CRUDListResourceType, CRUDResourceType
 from flama.resources.rest import RESTResource
 from flama.resources.routing import ResourceRoute, resource_method
+from flama.resources.workers import FlamaWorker
 from flama.sqlalchemy import SQLAlchemyModule
 from tests.conftest import DATABASE_URL
 
@@ -29,6 +29,10 @@ def app(app):
 def add_resources(app, resource):
     app.resources.add_resource("/puppy/", resource)
 
+    yield
+
+    del app.resources.worker._repositories[resource._meta.name]
+
 
 @pytest.fixture
 def puppy():
@@ -100,14 +104,18 @@ def custom_id_datetime_schema(self, app):
         return schema_
 
     @pytest.fixture
-    def custom_id_datetime_resource(self, custom_id_datetime_model, custom_id_datetime_schema):
+    def custom_id_datetime_resource(self, custom_id_datetime_model, custom_id_datetime_schema, app):
         class CustomUUIDResource(RESTResource, metaclass=CRUDListResourceType):
             model = custom_id_datetime_model
             schema = custom_id_datetime_schema
 
             name = "custom_id_datetime"
 
-        return CustomUUIDResource
+        app.resources.add_resource("/custom_id_datetime/", CustomUUIDResource)
+
+        yield CustomUUIDResource
+
+        del app.resources.worker._repositories[CustomUUIDResource._meta.name]
 
     @pytest.fixture
     async def custom_id_uuid_model(self, app):
@@ -156,14 +164,18 @@ def custom_id_uuid_schema(self, app):
         return schema_
 
     @pytest.fixture
-    def custom_id_uuid_resource(self, custom_id_uuid_model, custom_id_uuid_schema):
+    def custom_id_uuid_resource(self, custom_id_uuid_model, custom_id_uuid_schema, app):
         class CustomUUIDResource(RESTResource, metaclass=CRUDListResourceType):
             model = custom_id_uuid_model
             schema = custom_id_uuid_schema
 
             name = "custom_id_uuid"
 
-        return CustomUUIDResource
+        app.resources.add_resource("/custom_id_datetime/", CustomUUIDResource)
+
+        yield CustomUUIDResource
+
+        del app.resources.worker._repositories[CustomUUIDResource._meta.name]
 
     def test_crud_resource(self, resource, puppy_model, puppy_schema, app):
         expected_routes = [
@@ -289,8 +301,6 @@ async def test_delete_not_found(self, client, puppy):
 
     @pytest.mark.skipif(not DATABASE_URL.startswith("postgresql"), reason="Only valid for PostgreSQL backend")
     async def test_id_uuid(self, app, client, custom_id_uuid_resource):
-        app.resources.add_resource("/custom_id_datetime/", custom_id_uuid_resource)
-
         data = {"custom_id": str(uuid.uuid4()), "name": "foo"}
         expected_result = data.copy()
 
@@ -305,8 +315,6 @@ async def test_id_uuid(self, app, client, custom_id_uuid_resource):
         assert response.json() == expected_result
 
     async def test_id_datetime(self, client, app, custom_id_datetime_resource):
-        app.resources.add_resource("/custom_id_datetime/", custom_id_datetime_resource)
-
         data = {"custom_id": "2018-01-01T00:00:00", "name": "foo"}
         expected_result = data.copy()
 
@@ -332,11 +340,10 @@ class PuppyResource(RESTResource, metaclass=CRUDListResourceType):
             input_schema = puppy_schema
             output_schema = puppy_schema
 
-            @resource_method("/", methods=["GET"], name="puppy-list")
-            @paginator.page_number(schema_name="PuppyResource")
+            @resource_method("/", methods=["GET"], name="puppy-list", pagination="page_number")
             async def list(
                 self,
-                scope: types.Scope,
+                worker: FlamaWorker,
                 name: typing.Optional[str] = None,
                 custom_id__le: typing.Optional[int] = None,
                 **kwargs,
@@ -344,8 +351,6 @@ async def list(
                 """
                 description: Custom list method with filtering by name.
                 """
-                app = scope["app"]
-
                 clauses = []
 
                 if custom_id__le is not None:
@@ -356,7 +361,8 @@ async def list(
                 if name is not None:
                     filters["name"] = name
 
-                return await self._filter(app, *clauses, **filters)
+                async with worker:
+                    return await worker.repositories[self._meta.name].list(*clauses, **filters)
 
         return PuppyResource()
 
diff --git a/tests/resources/test_modules.py b/tests/resources/test_modules.py
index 24e61bf2..9b2d28f8 100644
--- a/tests/resources/test_modules.py
+++ b/tests/resources/test_modules.py
@@ -1,3 +1,5 @@
+from unittest.mock import Mock
+
 import pytest
 
 from flama.applications import Flama
@@ -30,16 +32,19 @@ class PuppyResource(BaseResource, metaclass=CRUDResourceType):
         resource = PuppyResource()
         app.resources.add_resource("/puppy/", resource, tags=tags)
 
-        assert len(app.routes) == 1
-        assert isinstance(app.routes[0], ResourceRoute)
-        resource_route = app.routes[0]
-        assert len(resource_route.routes) == 4
-        assert [(route.path, route.methods, route.endpoint, route.tags) for route in resource_route.routes] == [
-            ("/", {"POST"}, resource.create, {"tag": "create"}),
-            ("/{element_id}/", {"GET", "HEAD"}, resource.retrieve, {"tag": "retrieve"}),
-            ("/{element_id}/", {"PUT"}, resource.update, {"tag": "update"}),
-            ("/{element_id}/", {"DELETE"}, resource.delete, {"tag": "delete"}),
-        ]
+        try:
+            assert len(app.routes) == 1
+            assert isinstance(app.routes[0], ResourceRoute)
+            resource_route = app.routes[0]
+            assert len(resource_route.routes) == 4
+            assert [(route.path, route.methods, route.endpoint, route.tags) for route in resource_route.routes] == [
+                ("/", {"POST"}, resource.create, {"tag": "create"}),
+                ("/{element_id}/", {"GET", "HEAD"}, resource.retrieve, {"tag": "retrieve"}),
+                ("/{element_id}/", {"PUT"}, resource.update, {"tag": "update"}),
+                ("/{element_id}/", {"DELETE"}, resource.delete, {"tag": "delete"}),
+            ]
+        finally:
+            del app.resources.worker._repositories[PuppyResource._meta.name]
 
     def test_add_resource_decorator(self, app, puppy_model, puppy_schema, tags):
         class PuppyResource(BaseResource, metaclass=CRUDResourceType):
@@ -51,16 +56,23 @@ class PuppyResource(BaseResource, metaclass=CRUDResourceType):
             PuppyResource()
         )  # Apply decoration to an instance in order to check endpoints
 
-        assert len(app.routes) == 1
-        assert isinstance(app.routes[0], ResourceRoute)
-        resource_route = app.routes[0]
-        assert len(resource_route.routes) == 4
-        assert [(route.path, route.methods, route.endpoint, route.tags) for route in resource_route.routes] == [
-            ("/", {"POST"}, resource.create, {"tag": "create"}),
-            ("/{element_id}/", {"GET", "HEAD"}, resource.retrieve, {"tag": "retrieve"}),
-            ("/{element_id}/", {"PUT"}, resource.update, {"tag": "update"}),
-            ("/{element_id}/", {"DELETE"}, resource.delete, {"tag": "delete"}),
-        ]
+        try:
+            assert len(app.routes) == 1
+            assert isinstance(app.routes[0], ResourceRoute)
+            resource_route = app.routes[0]
+            assert len(resource_route.routes) == 4
+            assert [(route.path, route.methods, route.endpoint, route.tags) for route in resource_route.routes] == [
+                ("/", {"POST"}, resource.create, {"tag": "create"}),
+                ("/{element_id}/", {"GET", "HEAD"}, resource.retrieve, {"tag": "retrieve"}),
+                ("/{element_id}/", {"PUT"}, resource.update, {"tag": "update"}),
+                ("/{element_id}/", {"DELETE"}, resource.delete, {"tag": "delete"}),
+            ]
+        finally:
+            del app.resources.worker._repositories[PuppyResource._meta.name]
+
+    def test_add_resource_wrong(self, app):
+        with pytest.raises(ValueError, match=""):
+            app.resources.add_resource("/puppy/", Mock())
 
     def test_mount_resource_declarative(self, puppy_model, puppy_schema, tags):
         class PuppyResource(BaseResource, metaclass=CRUDResourceType):
@@ -72,16 +84,19 @@ class PuppyResource(BaseResource, metaclass=CRUDResourceType):
 
         app = Flama(routes=[route], schema=None, docs=None)
 
-        assert len(app.router.routes) == 1
-
-        assert len(app.routes) == 1
-        assert isinstance(app.routes[0], ResourceRoute)
-        resource_route = app.routes[0]
-        assert len(resource_route.routes) == 4
-        assert [(route.path, route.methods, route.endpoint, route.tags) for route in resource_route.routes] == [
-            ("/", {"POST"}, resource_route.resource.create, {"tag": "create"}),
-            ("/{element_id}/", {"GET", "HEAD"}, resource_route.resource.retrieve, {"tag": "retrieve"}),
-            ("/{element_id}/", {"PUT"}, resource_route.resource.update, {"tag": "update"}),
-            ("/{element_id}/", {"DELETE"}, resource_route.resource.delete, {"tag": "delete"}),
-        ]
-        assert isinstance(resource_route.resource, PuppyResource)
+        try:
+            assert len(app.router.routes) == 1
+            assert len(app.routes) == 1
+            assert isinstance(app.routes[0], ResourceRoute)
+            resource_route = app.routes[0]
+            assert len(resource_route.routes) == 4
+            assert [(route.path, route.methods, route.endpoint, route.tags) for route in resource_route.routes] == [
+                ("/", {"POST"}, resource_route.resource.create, {"tag": "create"}),
+                ("/{element_id}/", {"GET", "HEAD"}, resource_route.resource.retrieve, {"tag": "retrieve"}),
+                ("/{element_id}/", {"PUT"}, resource_route.resource.update, {"tag": "update"}),
+                ("/{element_id}/", {"DELETE"}, resource_route.resource.delete, {"tag": "delete"}),
+            ]
+            assert isinstance(resource_route.resource, PuppyResource)
+            assert PuppyResource._meta.name in app.resources.worker._repositories
+        finally:
+            del app.resources.worker._repositories[PuppyResource._meta.name]
diff --git a/tests/resources/test_resource.py b/tests/resources/test_resource.py
index 0f6e8388..1a279c2b 100644
--- a/tests/resources/test_resource.py
+++ b/tests/resources/test_resource.py
@@ -2,6 +2,7 @@
 import sqlalchemy
 
 from flama.applications import Flama
+from flama.ddd.repositories import SQLAlchemyRepository
 from flama.resources import data_structures
 from flama.resources.crud import CRUDListDropResourceType, CRUDListResourceType, CRUDResourceType
 from flama.resources.resource import BaseResource
@@ -35,7 +36,15 @@ def test_meta_attributes(self, resource, puppy_model, puppy_schema):
         assert hasattr(resource, "_meta")
         assert resource._meta.name == "puppy"
         assert resource._meta.verbose_name == "Puppy"
-        assert resource._meta.namespaces == {
+
+        namespaces = resource._meta.namespaces
+        ddd_namespace = namespaces.pop("ddd")
+
+        assert list(ddd_namespace.keys()) == ["repository"]
+        assert issubclass(ddd_namespace["repository"], SQLAlchemyRepository)
+        assert ddd_namespace["repository"].__name__ == "PuppyResourceRepository"
+
+        assert namespaces == {
             "rest": {
                 "model": data_structures.Model(
                     table=puppy_model, primary_key=data_structures.PrimaryKey(name="custom_id", type=int)
diff --git a/tests/resources/test_rest.py b/tests/resources/test_rest.py
index 7fa4e077..46fb07b5 100644
--- a/tests/resources/test_rest.py
+++ b/tests/resources/test_rest.py
@@ -2,6 +2,7 @@
 import sqlalchemy
 
 from flama.applications import Flama
+from flama.ddd.repositories import SQLAlchemyRepository
 from flama.resources import data_structures
 from flama.resources.rest import RESTResource, RESTResourceType
 from flama.sqlalchemy import SQLAlchemyModule, metadata
@@ -33,7 +34,14 @@ def test_meta_attributes(self, resource, puppy_model, puppy_schema):
         assert hasattr(resource, "_meta")
         assert resource._meta.name == "puppy"
         assert resource._meta.verbose_name == "Puppy"
-        assert resource._meta.namespaces == {
+        namespaces = resource._meta.namespaces
+        ddd_namespace = namespaces.pop("ddd")
+
+        assert list(ddd_namespace.keys()) == ["repository"]
+        assert issubclass(ddd_namespace["repository"], SQLAlchemyRepository)
+        assert ddd_namespace["repository"].__name__ == "PuppyResourceRepository"
+
+        assert namespaces == {
             "rest": {
                 "model": data_structures.Model(
                     table=puppy_model, primary_key=data_structures.PrimaryKey(name="custom_id", type=int)
diff --git a/tests/resources/test_workers.py b/tests/resources/test_workers.py
new file mode 100644
index 00000000..13d45e82
--- /dev/null
+++ b/tests/resources/test_workers.py
@@ -0,0 +1,50 @@
+from unittest.mock import Mock
+
+import pytest
+import sqlalchemy
+
+from flama import Flama
+from flama.ddd import SQLAlchemyRepository
+from flama.resources.workers import FlamaWorker
+from flama.sqlalchemy import SQLAlchemyModule
+
+
+class TestCaseFlamaWorker:
+    @pytest.fixture(scope="function")
+    def app(self):
+        return Flama(schema=None, docs=None, modules={SQLAlchemyModule("sqlite+aiosqlite://")})
+
+    @pytest.fixture(scope="function")
+    def worker(self, client):
+        class FooWorker(FlamaWorker):
+            ...
+
+        return FooWorker(client.app)
+
+    @pytest.fixture(scope="function")
+    def repository(self):
+        class FooRepository(SQLAlchemyRepository):
+            _table = Mock(spec=sqlalchemy.Table)
+
+        return FooRepository
+
+    def test_init(self, app):
+        worker = FlamaWorker()
+
+        assert not worker._init_repositories
+
+    def test_add_repository(self, worker, repository):
+        assert worker._repositories == {}
+
+        worker.add_repository("foo", repository)
+
+        assert worker._repositories == {"foo": repository}
+
+    async def test_async_context(self, worker, repository):
+        worker.add_repository("foo", repository)
+
+        with pytest.raises(AssertionError, match="Repositories not initialized"):
+            worker.repositories
+
+        async with worker:
+            assert worker.repositories == {"foo": repository(worker.connection)}
diff --git a/tests/schemas/conftest.py b/tests/schemas/conftest.py
new file mode 100644
index 00000000..e310a78e
--- /dev/null
+++ b/tests/schemas/conftest.py
@@ -0,0 +1,105 @@
+import typing as t
+from collections import namedtuple
+
+import marshmallow
+import pydantic
+import pytest
+import typesystem
+import typesystem.fields
+
+
+@pytest.fixture(scope="function")
+def foo_schema(app):
+    if app.schema.schema_library.lib == pydantic:
+        schema = pydantic.create_model("Foo", name=(str, ...))
+        name = "pydantic.main.Foo"
+    elif app.schema.schema_library.lib == typesystem:
+        schema = typesystem.Schema(title="Foo", fields={"name": typesystem.fields.String()})
+        name = "typesystem.schemas.Foo"
+    elif app.schema.schema_library.lib == marshmallow:
+        schema = type("Foo", (marshmallow.Schema,), {"name": marshmallow.fields.String()})
+        name = "abc.Foo"
+    else:
+        raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
+    return namedtuple("FooSchema", ("schema", "name"))(schema=schema, name=name)
+
+
+@pytest.fixture(scope="function")
+def bar_schema(app, foo_schema):
+    child_schema = foo_schema.schema
+    if app.schema.schema_library.lib == pydantic:
+        schema = pydantic.create_model("Bar", foo=(child_schema, ...))
+        name = "pydantic.main.Bar"
+    elif app.schema.schema_library.lib == typesystem:
+        schema = typesystem.Schema(
+            title="Bar",
+            fields={"foo": typesystem.Reference(to="Foo", definitions=typesystem.Definitions({"Foo": child_schema}))},
+        )
+        name = "typesystem.schemas.Bar"
+    elif app.schema.schema_library.lib == marshmallow:
+        schema = type("Bar", (marshmallow.Schema,), {"foo": marshmallow.fields.Nested(child_schema())})
+        name = "abc.Bar"
+    else:
+        raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
+    return namedtuple("BarSchema", ("schema", "name"))(schema=schema, name=name)
+
+
+@pytest.fixture(scope="function")
+def bar_list_schema(app, foo_schema):
+    child_schema = foo_schema.schema
+    if app.schema.schema_library.lib == pydantic:
+        schema = pydantic.create_model("BarList", foo=(t.List[child_schema], ...))
+        name = "pydantic.main.BarList"
+    elif app.schema.schema_library.lib == typesystem:
+        schema = typesystem.Schema(
+            title="BarList",
+            fields={
+                "foo": typesystem.Array(
+                    typesystem.Reference(to="Foo", definitions=typesystem.Definitions({"Foo": child_schema}))
+                )
+            },
+        )
+        name = "typesystem.schemas.BarList"
+    elif app.schema.schema_library.lib == marshmallow:
+        schema = type(
+            "BarList",
+            (marshmallow.Schema,),
+            {"foo": marshmallow.fields.List(marshmallow.fields.Nested(child_schema()))},
+        )
+        name = "abc.BarList"
+    else:
+        raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
+    return namedtuple("BarListSchema", ("schema", "name"))(schema=schema, name=name)
+
+
+@pytest.fixture(scope="function")
+def bar_dict_schema(app, foo_schema):
+    child_schema = foo_schema.schema
+    if app.schema.schema_library.lib == pydantic:
+        schema = pydantic.create_model("BarDict", foo=(t.Dict[str, child_schema], ...))
+        name = "pydantic.main.BarDict"
+    elif app.schema.schema_library.lib == typesystem:
+        schema = typesystem.Schema(
+            title="BarDict",
+            fields={
+                "foo": typesystem.Object(
+                    properties=typesystem.Reference(to="Foo", definitions=typesystem.Definitions({"Foo": child_schema}))
+                )
+            },
+        )
+        name = "typesystem.schemas.BarDict"
+    elif app.schema.schema_library.lib == marshmallow:
+        schema = type(
+            "BarDict",
+            (marshmallow.Schema,),
+            {"foo": marshmallow.fields.Dict(values=marshmallow.fields.Nested(child_schema()))},
+        )
+        name = "abc.BarDict"
+    else:
+        raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
+    return namedtuple("BarDictSchema", ("schema", "name"))(schema=schema, name=name)
+
+
+@pytest.fixture(scope="function")
+def schemas(foo_schema, bar_schema, bar_list_schema, bar_dict_schema):
+    return {"Foo": foo_schema, "Bar": bar_schema, "BarList": bar_list_schema, "BarDict": bar_dict_schema}
diff --git a/tests/schemas/test_data_structures.py b/tests/schemas/test_data_structures.py
new file mode 100644
index 00000000..59fe93c1
--- /dev/null
+++ b/tests/schemas/test_data_structures.py
@@ -0,0 +1,337 @@
+import datetime
+import functools
+import inspect
+import typing as t
+import uuid
+from copy import deepcopy
+from unittest.mock import Mock, call, patch
+
+import pytest
+
+from flama import types
+from flama.injection import Parameter as InjectionParameter
+from flama.schemas.data_structures import Field, Parameter, ParameterLocation, Schema
+from tests.schemas.test_generator import assert_recursive_contains
+
+
+class TestCaseField:
+    @pytest.mark.parametrize(
+        ["params", "result"],
+        (
+            pytest.param(
+                {"name": "foo", "type": int},
+                {
+                    "name": "foo",
+                    "type": int,
+                    "nullable": False,
+                    "multiple": False,
+                    "required": True,
+                    "default": InjectionParameter.empty,
+                },
+                id="default",
+            ),
+            pytest.param(
+                {"name": "foo", "type": t.Optional[int]},
+                {
+                    "name": "foo",
+                    "type": t.Optional[int],
+                    "nullable": True,
+                    "multiple": False,
+                    "required": True,
+                    "default": InjectionParameter.empty,
+                },
+                id="nullable",
+            ),
+            pytest.param(
+                {"name": "foo", "type": t.List[int]},
+                {
+                    "name": "foo",
+                    "type": t.List[int],
+                    "nullable": False,
+                    "multiple": True,
+                    "required": True,
+                    "default": InjectionParameter.empty,
+                },
+                id="multiple",
+            ),
+        ),
+    )
+    def test_init(self, params, result):
+        field = Field(**params)
+
+        for k, v in result.items():
+            assert getattr(field, k) == v
+
+    def test_from_parameter(self):
+        assert Field.from_parameter(InjectionParameter(name="foo", type=int, default=0)) == Field(
+            "foo", int, required=False, default=0
+        )
+
+    def test_is_field(self):
+        mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            Field.is_field(mock)
+            assert schemas_mock.adapter.is_field.call_args_list == [call(mock)]
+
+    @pytest.mark.parametrize(
+        ["type_", "result"],
+        (
+            pytest.param(int, True, id="int"),
+            pytest.param(float, True, id="float"),
+            pytest.param(str, True, id="str"),
+            pytest.param(bool, True, id="bool"),
+            pytest.param(uuid.UUID, True, id="uuid"),
+            pytest.param(datetime.date, True, id="date"),
+            pytest.param(datetime.time, True, id="date"),
+            pytest.param(datetime.datetime, True, id="datetime"),
+            pytest.param(types.QueryParam, True, id="query_param"),
+            pytest.param(types.PathParam, True, id="path_param"),
+            pytest.param(t.Optional[int], True, id="nullable"),
+            pytest.param(t.List[int], True, id="list"),
+            pytest.param(Mock, False, id="not_valid"),
+        ),
+    )
+    def test_is_http_valid_type(self, type_, result):
+        assert Field.is_http_valid_type(type_) == result
+
+    def test_json_schema(self):
+        field = Field("foo", int)
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            field.json_schema
+            assert schemas_mock.adapter.to_json_schema.call_args_list == [call(field.field)]
+
+
+class TestCaseSchema:
+    @pytest.fixture(scope="function")
+    def schema_type(self, request, foo_schema):
+        if request.param is None:
+            return None
+        elif request.param == "schema":
+            return foo_schema.schema
+        elif request.param == "schema_wrapped":
+            return types.Schema[foo_schema.schema]
+        elif request.param == "list":
+            return t.List[foo_schema.schema] if inspect.isclass(foo_schema.schema) else foo_schema.schema
+        else:
+            raise ValueError("Wrong schema type")
+
+    @pytest.mark.parametrize(
+        ["schema_type", "exception"],
+        (
+            pytest.param("schema", None, id="schema"),
+            pytest.param("schema_wrapped", None, id="schema_wrapped"),
+            pytest.param("list", None, id="list"),
+            pytest.param(None, ValueError("Wrong schema type"), id="wrong"),
+        ),
+        indirect=["schema_type", "exception"],
+    )
+    def test_from_type(self, foo_schema, schema_type, exception):
+        with exception:
+            schema = Schema.from_type(schema_type)
+
+            assert schema.schema == foo_schema.schema
+
+    def test_build(self, foo_schema):
+        n = Mock()
+        s = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            schemas_mock.adapter.build_schema.return_value = foo_schema.schema
+
+            schema = Schema.build(n, s)
+
+            assert schemas_mock.adapter.build_schema.call_args_list == [call(name=n, schema=s, fields={})]
+            assert schema.schema == foo_schema.schema
+
+    def test_is_schema(self):
+        mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            Schema.is_schema(mock)
+
+            assert schemas_mock.adapter.is_schema.call_args_list == [call(mock)]
+
+    def test_name(self):
+        mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            Schema(mock).name
+
+            assert schemas_mock.adapter.name.call_args_list == [call(mock)]
+
+    @pytest.mark.parametrize(
+        ["schema", "json_schema", "key_to_replace"],
+        (
+            pytest.param(
+                "Foo",
+                {"properties": {"name": {"type": "string"}}, "type": "object"},
+                None,
+                id="no_nested",
+            ),
+            pytest.param(
+                "Bar",
+                {"properties": {"foo": {"$ref": "#/components/schemas/Foo"}}, "type": "object"},
+                "properties.foo",
+                id="attribute_nested",
+            ),
+            pytest.param(
+                "BarList",
+                {
+                    "properties": {"foo": {"items": {"$ref": "#/components/schemas/Foo"}, "type": "array"}},
+                    "type": "object",
+                },
+                "properties.foo.items",
+                id="list_nested",
+            ),
+            pytest.param(
+                "BarDict",
+                {
+                    "properties": {
+                        "foo": {"additionalProperties": {"$ref": "#/components/schemas/Foo"}, "type": "object"}
+                    },
+                    "type": "object",
+                },
+                "properties.foo.additionalProperties",
+                id="dict_nested",
+            ),
+        ),
+    )
+    def test_json_schema(self, schemas, schema, json_schema, key_to_replace):
+        result = Schema(schemas[schema].schema).json_schema
+
+        expected_result = deepcopy(json_schema)
+
+        if key_to_replace:
+            subdict = functools.reduce(lambda x, k: x[k], key_to_replace.split("."), expected_result)
+            subdict["$ref"] = subdict["$ref"].replace("Foo", schemas["Foo"].name)
+
+        assert_recursive_contains(expected_result, result)
+
+    def test_unique_schema(self):
+        mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            Schema(mock).unique_schema
+
+            assert schemas_mock.adapter.unique_schema.call_args_list == [call(mock)]
+
+    def test_fields(self):
+        mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            schemas_mock.adapter.unique_schema.return_value = mock
+
+            Schema(mock).fields
+
+            assert schemas_mock.adapter.unique_schema.call_args_list == [call(mock)]
+            assert schemas_mock.adapter.schema_fields.call_args_list == [call(mock)]
+
+    @pytest.mark.parametrize(
+        ["schema", "nested_schemas"],
+        (
+            pytest.param("Foo", [], id="no_nested"),
+            pytest.param("Bar", ["Foo"], id="attribute_nested"),
+            pytest.param("BarList", ["Foo"], id="list_nested"),
+            pytest.param("BarDict", ["Foo"], id="dict_nested"),
+        ),
+    )
+    def test_nested_schemas(self, schemas, schema, nested_schemas):
+        result = Schema(schemas[schema].schema).nested_schemas()
+
+        assert result == [schemas[x].schema for x in nested_schemas]
+
+    @pytest.mark.parametrize(
+        ["values", "expected_result"],
+        (
+            pytest.param(Mock(), True, id="single"),
+            pytest.param([Mock(), Mock()], [True, True], id="multiple"),
+        ),
+    )
+    def test_validate(self, values, expected_result):
+        schema_mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            schemas_mock.adapter.validate.return_value = True
+
+            result = Schema(schema_mock).validate(values)
+
+            assert result == expected_result
+            assert schemas_mock.adapter.validate.call_args_list == [
+                call(schema_mock, x) for x in (values if isinstance(values, list) else [values])
+            ]
+
+    @pytest.mark.parametrize(
+        ["values", "expected_result"],
+        (
+            pytest.param(Mock(), True, id="single"),
+            pytest.param([Mock(), Mock()], [True, True], id="multiple"),
+        ),
+    )
+    def test_load(self, values, expected_result):
+        schema_mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            schemas_mock.adapter.load.return_value = True
+
+            result = Schema(schema_mock).load(values)
+
+            assert result == expected_result
+            assert schemas_mock.adapter.load.call_args_list == [
+                call(schema_mock, x) for x in (values if isinstance(values, list) else [values])
+            ]
+
+    @pytest.mark.parametrize(
+        ["values", "expected_result"],
+        (
+            pytest.param(Mock(), True, id="single"),
+            pytest.param([Mock(), Mock()], [True, True], id="multiple"),
+        ),
+    )
+    def test_dump(self, values, expected_result):
+        schema_mock = Mock()
+        with patch("flama.schemas.data_structures.schemas") as schemas_mock:
+            schemas_mock.adapter.dump.return_value = True
+
+            result = Schema(schema_mock).dump(values)
+
+            assert result == expected_result
+            assert schemas_mock.adapter.dump.call_args_list == [
+                call(schema_mock, x) for x in (values if isinstance(values, list) else [values])
+            ]
+
+
+class TestCaseParameter:
+    @pytest.mark.parametrize(
+        ["type_", "parameter", "result"],
+        (
+            pytest.param(
+                "path",
+                InjectionParameter("foo", str),
+                Parameter(name="foo", location=ParameterLocation.path, type=str),
+                id="path",
+            ),
+            pytest.param(
+                "query",
+                InjectionParameter("foo", int),
+                Parameter(name="foo", location=ParameterLocation.query, type=int),
+                id="query",
+            ),
+            pytest.param(
+                "body",
+                InjectionParameter("foo", types.Schema),
+                Parameter(name="foo", location=ParameterLocation.body, type=types.Schema),
+                id="body",
+            ),
+            pytest.param(
+                "response",
+                InjectionParameter("foo", types.Schema),
+                Parameter(name="foo", location=ParameterLocation.response, type=types.Schema),
+                id="response",
+            ),
+        ),
+    )
+    def test_build(self, foo_schema, type_, parameter, result):
+        if parameter.type == types.Schema:
+            parameter = InjectionParameter(parameter.name, types.Schema[foo_schema.schema], parameter.default)
+            result = Parameter(
+                name=result.name,
+                location=result.location,
+                type=types.Schema[foo_schema.schema],
+                required=result.required,
+                default=result.default,
+            )
+
+        assert Parameter.build(type_, parameter) == result
diff --git a/tests/schemas/test_generator.py b/tests/schemas/test_generator.py
index 726a2672..f65b52ab 100644
--- a/tests/schemas/test_generator.py
+++ b/tests/schemas/test_generator.py
@@ -1,5 +1,4 @@
 import contextlib
-import typing as t
 from collections import namedtuple
 
 import marshmallow
@@ -16,112 +15,28 @@
 from tests.asserts import assert_recursive_contains
 
 
-class TestCaseSchemaRegistry:
-    @pytest.fixture(scope="function")
-    def registry(self):
-        return SchemaRegistry()
+def _fix_ref(value, refs):
+    try:
+        prefix, name = value.rsplit("/", 1)
+        return f"{prefix}/{refs[name]}"
+    except KeyError:
+        return value
 
-    @pytest.fixture(scope="function")
-    def foo_schema(self, app):
-        if app.schema.schema_library.lib == pydantic:
-            schema = pydantic.create_model("Foo", name=(str, ...))
-            name = "pydantic.main.Foo"
-        elif app.schema.schema_library.lib == typesystem:
-            schema = typesystem.Schema(title="Foo", fields={"name": typesystem.fields.String()})
-            name = "typesystem.schemas.Foo"
-        elif app.schema.schema_library.lib == marshmallow:
-            schema = type("Foo", (marshmallow.Schema,), {"name": marshmallow.fields.String()})
-            name = "abc.Foo"
-        else:
-            raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
-        return namedtuple("FooSchema", ("schema", "name"))(schema=schema, name=name)
 
-    @pytest.fixture(scope="function")
-    def foo_array_schema(self, foo_schema):
-        return t.List[foo_schema.schema]
+def _replace_refs(schema, refs):
+    if isinstance(schema, dict):
+        return {k: _fix_ref(v, refs) if k == "$ref" else _replace_refs(v, refs) for k, v in schema.items()}
 
-    @pytest.fixture(scope="function")
-    def bar_schema(self, app, foo_schema):
-        child_schema = foo_schema.schema
-        if app.schema.schema_library.lib == pydantic:
-            schema = pydantic.create_model("Bar", foo=(child_schema, ...))
-            name = "pydantic.main.Bar"
-        elif app.schema.schema_library.lib == typesystem:
-            schema = typesystem.Schema(
-                title="Bar",
-                fields={
-                    "foo": typesystem.Reference(to="Foo", definitions=typesystem.Definitions({"Foo": child_schema}))
-                },
-            )
-            name = "typesystem.schemas.Bar"
-        elif app.schema.schema_library.lib == marshmallow:
-            schema = type("Bar", (marshmallow.Schema,), {"foo": marshmallow.fields.Nested(child_schema())})
-            name = "abc.Bar"
-        else:
-            raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
-        return namedtuple("BarSchema", ("schema", "name"))(schema=schema, name=name)
+    if isinstance(schema, (list, tuple, set)):
+        return [_replace_refs(x, refs) for x in schema]
 
-    @pytest.fixture(scope="function")
-    def bar_list_schema(self, app, foo_schema):
-        child_schema = foo_schema.schema
-        if app.schema.schema_library.lib == pydantic:
-            schema = pydantic.create_model("BarList", foo=(t.List[child_schema], ...))
-            name = "pydantic.main.BarList"
-        elif app.schema.schema_library.lib == typesystem:
-            schema = typesystem.Schema(
-                title="BarList",
-                fields={
-                    "foo": typesystem.Array(
-                        typesystem.Reference(to="Foo", definitions=typesystem.Definitions({"Foo": child_schema}))
-                    )
-                },
-            )
-            name = "typesystem.schemas.BarList"
-        elif app.schema.schema_library.lib == marshmallow:
-            schema = type(
-                "BarList",
-                (marshmallow.Schema,),
-                {"foo": marshmallow.fields.List(marshmallow.fields.Nested(child_schema()))},
-            )
-            name = "abc.BarList"
-        else:
-            raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
-        return namedtuple("BarListSchema", ("schema", "name"))(schema=schema, name=name)
+    return schema
 
-    @pytest.fixture(scope="function")
-    def bar_dict_schema(self, app, foo_schema):
-        child_schema = foo_schema.schema
-        if app.schema.schema_library.lib == pydantic:
-            schema = pydantic.create_model("BarDict", foo=(t.Dict[str, child_schema], ...))
-            name = "pydantic.main.BarDict"
-        elif app.schema.schema_library.lib == typesystem:
-            schema = typesystem.Schema(
-                title="BarDict",
-                fields={
-                    "foo": typesystem.Object(
-                        properties={
-                            "x": typesystem.Reference(
-                                to="Foo", definitions=typesystem.Definitions({"Foo": child_schema})
-                            )
-                        }
-                    )
-                },
-            )
-            name = "typesystem.schemas.BarDict"
-        elif app.schema.schema_library.lib == marshmallow:
-            schema = type(
-                "BarDict",
-                (marshmallow.Schema,),
-                {"foo": marshmallow.fields.Dict(values=marshmallow.fields.Nested(child_schema()))},
-            )
-            name = "abc.BarDict"
-        else:
-            raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
-        return namedtuple("BarDictSchema", ("schema", "name"))(schema=schema, name=name)
 
+class TestCaseSchemaRegistry:
     @pytest.fixture(scope="function")
-    def schemas(self, foo_schema, bar_schema, bar_list_schema, bar_dict_schema):
-        return {"Foo": foo_schema, "Bar": bar_schema, "BarList": bar_list_schema, "BarDict": bar_dict_schema}
+    def registry(self):
+        return SchemaRegistry()
 
     @pytest.fixture(scope="function")
     def spec(self):
@@ -933,7 +848,6 @@ def owner_schema(self, app):
             name = "abc.Owner"
         else:
             raise ValueError("Wrong schema lib")
-        app.schema.schemas["Owner"] = schema
         return namedtuple("OwnerSchema", ("schema", "name"))(schema, name)
 
     @pytest.fixture(scope="function")
@@ -964,7 +878,6 @@ def puppy_schema(self, app, owner_schema):
             name = "abc.Puppy"
         else:
             raise ValueError("Wrong schema lib")
-        app.schema.schemas["Puppy"] = schema
         return namedtuple("PuppySchema", ("schema", "name"))(schema, name)
 
     @pytest.fixture(scope="function")
@@ -980,10 +893,12 @@ def body_param_schema(self, app):
             name = "abc.BodyParam"
         else:
             raise ValueError("Wrong schema lib")
-
-        app.schema.schemas["BodyParam"] = schema
         return namedtuple("BodyParamSchema", ("schema", "name"))(schema, name)
 
+    @pytest.fixture(scope="function")
+    def schemas(self, owner_schema, puppy_schema, body_param_schema):
+        return {"Owner": owner_schema, "Puppy": puppy_schema, "BodyParam": body_param_schema}
+
     @pytest.fixture(scope="function", autouse=True)
     def add_endpoints(self, app, puppy_schema, body_param_schema):
         @app.route("/endpoint/", methods=["GET"])
@@ -1083,11 +998,16 @@ def test_schema_info(self, app):
         assert schema["version"] == "0.1"
         assert schema["description"] == "Bar"
 
-    def test_components_schemas(self, app):
-        schemas = app.schema.schema["components"]["schemas"]
+    def test_components_schemas(self, app, schemas):
+        used_schemas = app.schema.schema["components"]["schemas"]
 
         # Check declared components are only those that are in use
-        assert set(schemas.keys()) == {"Owner", "Puppy", "BodyParam", "flama.APIError"}
+        assert set(used_schemas.keys()) == {
+            schemas["Owner"].name,
+            schemas["Puppy"].name,
+            schemas["BodyParam"].name,
+            "flama.APIError",
+        }
 
     @pytest.mark.parametrize(
         "path,verb,expected_schema",
@@ -1355,5 +1275,7 @@ def test_components_schemas(self, app):
             ),
         ],
     )
-    def test_schema(self, app, path, verb, expected_schema):
-        assert_recursive_contains(expected_schema, app.schema.schema["paths"][path][verb])
+    def test_schema(self, app, schemas, path, verb, expected_schema):
+        schema = _replace_refs(expected_schema, {k: v.name for k, v in schemas.items()})
+
+        assert_recursive_contains(schema, app.schema.schema["paths"][path][verb])
diff --git a/tests/schemas/test_routing.py b/tests/schemas/test_routing.py
index 44c884c9..a899e9db 100644
--- a/tests/schemas/test_routing.py
+++ b/tests/schemas/test_routing.py
@@ -4,6 +4,7 @@
 import pydantic
 import pytest
 import typesystem
+import typesystem.fields
 
 import flama.types.websockets
 from flama import Component, HTTPEndpoint, Route, WebSocketEndpoint, WebSocketRoute, types, websockets
diff --git a/tests/test_applications.py b/tests/test_applications.py
index 6aaa1589..6d4301f6 100644
--- a/tests/test_applications.py
+++ b/tests/test_applications.py
@@ -1,8 +1,9 @@
-from unittest.mock import AsyncMock, MagicMock, PropertyMock, call, patch
+from unittest.mock import AsyncMock, MagicMock, call, patch
 
 import pytest
 
 from flama import Component, Flama, Module, Mount, Route, Router, exceptions, http, types, websockets
+from flama.ddd.components import WorkerComponent
 from flama.events import Events
 from flama.injection.injector import Injector
 from flama.middleware import Middleware, MiddlewareStack
@@ -69,9 +70,11 @@ def test_init(self, module, component):
         assert isinstance(app.middleware, MiddlewareStack)
         assert app.middleware
         # Check modules
-        assert app.modules == {ResourcesModule, SchemaModule, ModelsModule, module}
+        assert app.modules == {*DEFAULT_MODULES, module}
         # Check components
-        assert app.components == [component_obj]
+        default_components = app.components[:1]
+        assert isinstance(default_components[0], WorkerComponent)
+        assert app.components == [*default_components, component_obj]
         # Check events
         assert app.events == Events(
             startup=[m.on_startup for m in app.modules.values()], shutdown=[m.on_shutdown for m in app.modules.values()]
@@ -81,6 +84,8 @@ def test_getattr(self, app):
         for name, module in app.modules.items():
             assert getattr(app, name) == module
 
+        assert getattr(app, "wrong") is None
+
     async def test_call(self, app, asgi_scope, asgi_receive, asgi_send):
         with patch.object(app, "middleware", new=AsyncMock(spec=MiddlewareStack)):
             await app(asgi_scope, asgi_receive, asgi_send)
@@ -89,14 +94,12 @@ async def test_call(self, app, asgi_scope, asgi_receive, asgi_send):
             assert app.middleware.call_args_list == [call(asgi_scope, asgi_receive, asgi_send)]
 
     def test_components(self, app):
-        expected_components = [MagicMock()]
-        components_mock = PropertyMock(return_value=expected_components)
+        component = MagicMock(spec=Component)
 
-        with patch.object(Router, "components", new=components_mock):
-            components = app.components
+        app.router.add_component(component)
 
-        assert components_mock.call_args_list == [call()]
-        assert components == expected_components
+        default_components = app.components[:1]
+        assert app.components == [*default_components, component]
 
     def test_add_component(self, app, component):
         component_obj = component()
@@ -124,7 +127,17 @@ def foo():
             route = app.add_route("/", foo, tags=tags)
 
         assert router_mock.add_route.call_args_list == [
-            call("/", foo, methods=None, name=None, include_in_schema=True, route=None, root=app, tags=tags)
+            call(
+                "/",
+                foo,
+                methods=None,
+                name=None,
+                include_in_schema=True,
+                route=None,
+                root=app,
+                pagination=None,
+                tags=tags,
+            )
         ]
         assert route == foo
 
@@ -136,7 +149,7 @@ def foo():
                 ...
 
         assert router_mock.route.call_args_list == [
-            call("/", methods=None, name=None, include_in_schema=True, root=app, tags=tags)
+            call("/", methods=None, name=None, include_in_schema=True, root=app, pagination=None, tags=tags)
         ]
 
     def test_add_websocket_route(self, app, tags):
@@ -148,7 +161,7 @@ def foo():
             route = app.add_websocket_route("/", foo, tags=tags)
 
         assert router_mock.add_websocket_route.call_args_list == [
-            call("/", foo, name=None, route=None, root=app, tags=tags)
+            call("/", foo, name=None, route=None, root=app, pagination=None, tags=tags)
         ]
         assert route == foo
 
@@ -159,7 +172,9 @@ def test_websocket_route(self, app, tags):
             def foo():
                 ...
 
-        assert router_mock.websocket_route.call_args_list == [call("/", name=None, root=app, tags=tags)]
+        assert router_mock.websocket_route.call_args_list == [
+            call("/", name=None, root=app, pagination=None, tags=tags)
+        ]
 
     def test_mount(self, app, tags):
         expected_mount = MagicMock()
@@ -253,20 +268,23 @@ def custom(self):
         with exception:
             assert app.resolve_url(resolve, **path_params) == resolution
 
-    def test_end_to_end(self, component, module):
-        """Test"""
-        root_app = Flama(schema=None, docs=None)
+    def test_end_to_end(self, module):
+        root_component = MagicMock(spec=Component)
+        root_app = Flama(schema=None, docs=None, components=[root_component])
         root_app.add_get("/foo", lambda: {})
 
         assert len(root_app.router.routes) == 1
-        assert root_app.components == set()
+        root_default_components = root_app.components[:1]
+        assert root_app.components == [*root_default_components, root_component]
         assert root_app.modules == DEFAULT_MODULES
 
-        leaf_app = Flama(schema=None, docs=None, components=[component], modules={module()})
+        leaf_component = MagicMock(spec=Component)
+        leaf_app = Flama(schema=None, docs=None, components=[leaf_component], modules={module()})
         leaf_app.add_get("/bar", lambda: {})
 
         assert len(leaf_app.router.routes) == 1
-        assert leaf_app.components == [component]
+        leaf_default_components = leaf_app.components[:1]
+        assert leaf_app.components == [*leaf_default_components, leaf_component]
         assert leaf_app.modules == [*DEFAULT_MODULES, module]
 
         root_app.mount("/app", app=leaf_app)
@@ -282,17 +300,19 @@ def test_end_to_end(self, component, module):
         assert isinstance(mount_app.app, Router)
         mount_router = mount_app.app
         # Check components are collected across the entire tree
-        assert mount_router.components == [component]
-        assert root_app.components == [component]
+        assert root_app.components == [*root_default_components, root_component]
+        assert mount_router.components == [*leaf_default_components, leaf_component, *root_app.components]
         # Check modules are isolated for each app
         assert mount_app.modules == [*DEFAULT_MODULES, module]
         assert root_app.modules == DEFAULT_MODULES
 
-    def test_end_to_end_declarative(self, component, module):
+    def test_end_to_end_declarative(self, module):
+        leaf_component = MagicMock(spec=Component)
         leaf_routes = [Route("/bar", lambda: {})]
-        leaf_app = Flama(routes=leaf_routes, schema=None, docs=None, components=[component], modules={module()})
+        leaf_app = Flama(routes=leaf_routes, schema=None, docs=None, components=[leaf_component], modules={module()})
+        root_component = MagicMock(spec=Component)
         root_routes = [Route("/foo", lambda: {}), Mount("/app", app=leaf_app)]
-        root_app = Flama(routes=root_routes, schema=None, docs=None)
+        root_app = Flama(routes=root_routes, schema=None, docs=None, components=[root_component])
 
         assert len(root_app.router.routes) == 2
         # Check mount is initialized
@@ -305,8 +325,10 @@ def test_end_to_end_declarative(self, component, module):
         assert isinstance(mount_app.app, Router)
         mount_router = mount_app.app
         # Check components are collected across the entire tree
-        assert mount_router.components == [component]
-        assert root_app.components == [component]
+        root_default_components = root_app.components[:1]
+        assert root_app.components == [*root_default_components, root_component]
+        leaf_default_components = leaf_app.components[:1]
+        assert mount_router.components == [*leaf_default_components, leaf_component, *root_app.components]
         # Check modules are isolated for each app
         assert mount_app.modules == [*DEFAULT_MODULES, module]
         assert root_app.modules == DEFAULT_MODULES
diff --git a/tests/test_background.py b/tests/test_background.py
index 6fbffe01..bc942fa7 100644
--- a/tests/test_background.py
+++ b/tests/test_background.py
@@ -38,7 +38,7 @@ async def test():
         assert response.status_code == 200
         assert response.json() == {"foo": "bar"}
 
-        assert process_event.wait(1.0)
+        assert process_event.wait(5.0)
 
     async def test_background_thread_task(self, app, client, task, thread_event):
         @app.route("/")
@@ -49,7 +49,7 @@ async def test():
         assert response.status_code == 200
         assert response.json() == {"foo": "bar"}
 
-        assert thread_event.wait(1.0)
+        assert thread_event.wait(5.0)
 
 
 class TestCaseBackgroundTasks:
@@ -65,5 +65,5 @@ async def test():
         assert response.status_code == 200
         assert response.json() == {"foo": "bar"}
 
-        assert process_event.wait(1.0)
-        assert thread_event.wait(1.0)
+        assert process_event.wait(5.0)
+        assert thread_event.wait(5.0)
diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py
index 597f317f..ada090ce 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -1,10 +1,16 @@
 import functools
 import multiprocessing
 
+import pytest
+
 from flama import concurrency
 
 
-async def task(event):
+def sync_event_task(event):
+    event.set()
+
+
+async def async_event_task(event):
     event.set()
 
 
@@ -76,11 +82,20 @@ async def bar():
 
 
 class TestCaseAsyncProcess:
-    async def test_async_process(self):
+    @pytest.mark.parametrize(
+        ["target"],
+        (
+            pytest.param(sync_event_task, id="sync"),
+            pytest.param(async_event_task, id="async"),
+            pytest.param(None, id="no_target"),
+        ),
+    )
+    async def test_async_process(self, target):
         event = multiprocessing.Event()
 
-        process = concurrency.AsyncProcess(target=task, args=(event,))
+        process = concurrency.AsyncProcess(target=target, args=(event,))
         process.start()
         process.join()
 
-        assert event.wait(1.0)
+        if target:
+            assert event.wait(5.0)
diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py
index 396d8bf9..046d52e2 100644
--- a/tests/test_endpoints.py
+++ b/tests/test_endpoints.py
@@ -5,6 +5,7 @@
 import pytest
 import starlette.websockets
 import typesystem
+import typesystem.fields
 
 from flama import Component, exceptions, types, websockets
 from flama.endpoints import HTTPEndpoint, WebSocketEndpoint
@@ -100,16 +101,18 @@ def get(self):
                     ...
 
             route = app.add_route("/", FooEndpoint)
-            asgi_scope = {
-                **asgi_scope,
-                "app": app,
-                "type": "http",
-                "method": "GET",
-                "path": "/",
-                "path_params": {},
-                "endpoint": FooEndpoint,
-                "route": route,
-            }
+            asgi_scope = types.Scope(
+                {
+                    **asgi_scope,
+                    "app": app,
+                    "type": "http",
+                    "method": "GET",
+                    "path": "/",
+                    "path_params": {},
+                    "endpoint": FooEndpoint,
+                    "route": route,
+                }
+            )
             endpoint = HTTPEndpoint(asgi_scope, asgi_receive, asgi_send)
             assert endpoint.state == {
                 "scope": asgi_scope,
@@ -131,6 +134,12 @@ def test_await(self, endpoint):
     def test_allowed_methods(self, endpoint):
         assert endpoint.allowed_methods() == {"GET", "HEAD"}
 
+        class BarEndpoint(HTTPEndpoint):
+            def post(self):
+                ...
+
+        assert BarEndpoint.allowed_methods() == {"POST"}
+
     def test_handler(self, endpoint):
         endpoint.state["request"].scope["method"] = "GET"
         assert endpoint.handler == endpoint.get
@@ -226,7 +235,7 @@ async def on_receive(self, websocket: websockets.WebSocket, data: types.Data):
     def test_injecting_component(self, app, client):
         @app.websocket_route("/")
         class FooWebSocketEndpoint(WebSocketEndpoint):
-            encoding = "bytes"
+            encoding = types.Encoding("bytes")
 
             async def on_connect(self, websocket: websockets.WebSocket):
                 await websocket.accept()
@@ -264,20 +273,22 @@ async def on_receive(self, websocket: websockets.WebSocket, data: types.Data):
             ws.send_bytes("foo")
             result = ws.receive()
 
-        assert result == {"code": 1011, "type": "websocket.close", "reason": ""}
+            assert result == {"code": 1011, "type": "websocket.close", "reason": ""}
 
     def test_init(self, app, asgi_scope, asgi_receive, asgi_send):
         with patch("flama.endpoints.websockets.WebSocket") as websocket_mock:
             route = app.add_websocket_route("/", WebSocketEndpoint)
-            asgi_scope = {
-                **asgi_scope,
-                "app": app,
-                "type": "websocket",
-                "path": "/",
-                "path_params": {},
-                "endpoint": WebSocketEndpoint,
-                "route": route,
-            }
+            asgi_scope = types.Scope(
+                {
+                    **asgi_scope,
+                    "app": app,
+                    "type": "websocket",
+                    "path": "/",
+                    "path_params": {},
+                    "endpoint": WebSocketEndpoint,
+                    "route": route,
+                }
+            )
             endpoint = WebSocketEndpoint(asgi_scope, asgi_receive, asgi_send)
             assert endpoint.state == {
                 "scope": asgi_scope,
@@ -299,6 +310,13 @@ def test_await(self, endpoint):
 
             assert endpoint.dispatch.call_args_list == [call()]
 
+    def test_allowed_handlers(self, endpoint):
+        assert endpoint.allowed_handlers() == {
+            "WEBSOCKET_CONNECT": endpoint.__class__.on_connect,
+            "WEBSOCKET_RECEIVE": endpoint.__class__.on_receive,
+            "WEBSOCKET_DISCONNECT": endpoint.__class__.on_disconnect,
+        }
+
     @pytest.mark.parametrize(
         ["endpoint_receive", "websocket_receive", "exception", "result_code", "result_message"],
         (
diff --git a/tests/test_http.py b/tests/test_http.py
index 2d71d9ad..dd137641 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -1,3 +1,4 @@
+import dataclasses
 import datetime
 import enum
 import json
@@ -8,7 +9,12 @@
 import jinja2
 import pytest
 
-from flama import exceptions, http, schemas
+from flama import exceptions, http, schemas, types
+
+
+@dataclasses.dataclass
+class Foo:
+    bar: int
 
 
 class TestCaseRequest:
@@ -49,31 +55,45 @@ def schema(self):
         return Mock()
 
     @pytest.mark.parametrize(
-        ["content", "result"],
+        ["content", "result", "exception"],
         (
-            pytest.param({"foo": pathlib.Path("foo/bar.json")}, {"foo": "foo/bar.json"}, id="path"),
-            pytest.param({"foo": b"bar"}, {"foo": "bar"}, id="bytes"),
-            pytest.param({"foo": bytearray([1, 2, 3])}, {"foo": "\x01\x02\x03"}, id="bytearray"),
-            pytest.param({"foo": enum.Enum("Foo", ["bar"]).bar}, {"foo": 1}, id="enum"),
-            pytest.param({"foo": uuid.UUID(int=0)}, {"foo": "00000000-0000-0000-0000-000000000000"}, id="uuid"),
             pytest.param(
-                {"foo": datetime.datetime(2023, 9, 20, 11, 30, 0)}, {"foo": "2023-09-20T11:30:00"}, id="datetime"
+                {"foo": {"bar": [1, "foobar", 2.0, True, None]}},
+                {"foo": {"bar": [1, "foobar", 2.0, True, None]}},
+                None,
+                id="default",
             ),
-            pytest.param({"foo": datetime.date(2023, 9, 20)}, {"foo": "2023-09-20"}, id="date"),
-            pytest.param({"foo": datetime.time(11, 30, 0)}, {"foo": "11:30:00"}, id="time"),
+            pytest.param({"foo": pathlib.Path("foo/bar.json")}, {"foo": "foo/bar.json"}, None, id="path"),
+            pytest.param({"foo": b"bar"}, {"foo": "bar"}, None, id="bytes"),
+            pytest.param({"foo": bytearray([1, 2, 3])}, {"foo": "\x01\x02\x03"}, None, id="bytearray"),
+            pytest.param({"foo": enum.Enum("Foo", ["bar"]).bar}, {"foo": 1}, None, id="enum"),
+            pytest.param({"foo": uuid.UUID(int=0)}, {"foo": "00000000-0000-0000-0000-000000000000"}, None, id="uuid"),
+            pytest.param({"foo": {"bar"}}, {"foo": ["bar"]}, None, id="set"),
+            pytest.param({"foo": frozenset({"bar"})}, {"foo": ["bar"]}, None, id="set"),
+            pytest.param(
+                {"foo": datetime.datetime(2023, 9, 20, 11, 30, 0)}, {"foo": "2023-09-20T11:30:00"}, None, id="datetime"
+            ),
+            pytest.param({"foo": datetime.date(2023, 9, 20)}, {"foo": "2023-09-20"}, None, id="date"),
+            pytest.param({"foo": datetime.time(11, 30, 0)}, {"foo": "11:30:00"}, None, id="time"),
             pytest.param(
                 {"foo": datetime.timedelta(days=1, hours=20, minutes=30, seconds=10, milliseconds=10, microseconds=6)},
                 {"foo": "P1D20H30M10.010006S"},
+                None,
                 id="timedelta",
             ),
-            pytest.param({"foo": Exception}, {"foo": "Exception"}, id="exception_class"),
-            pytest.param({"foo": Exception("bar")}, {"foo": "Exception('bar')"}, id="exception_obj"),
+            pytest.param({"foo": Exception}, {"foo": "Exception"}, None, id="exception_class"),
+            pytest.param({"foo": Exception("bar")}, {"foo": "Exception('bar')"}, None, id="exception_obj"),
+            pytest.param(types.Schema({"foo": "bar"}), {"foo": "bar"}, None, id="schema"),
+            pytest.param({"foo": Foo(bar=1)}, {"foo": {"bar": 1}}, None, id="dataclass"),
+            pytest.param({"foo": Mock()}, None, TypeError, id="error"),
         ),
+        indirect=["exception"],
     )
-    def test_render(self, content, result):
-        response = http.JSONResponse(content=content)
+    def test_render(self, content, result, exception):
+        with exception:
+            response = http.JSONResponse(content=content)
 
-        assert json.loads(response.body.decode()) == result
+            assert json.loads(response.body.decode()) == result
 
 
 class TestCaseAPIResponse:
diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py
index bb72b475..8d96d34f 100644
--- a/tests/test_lifespan.py
+++ b/tests/test_lifespan.py
@@ -21,7 +21,10 @@ def lifespan(self):
             pytest.param(
                 None,
                 None,
-                [call({"type": "lifespan.startup.complete"}), call({"type": "lifespan.shutdown.complete"})],
+                [
+                    call({"type": "lifespan.startup.complete"}),
+                    call({"type": "lifespan.shutdown.complete"}),
+                ],
                 types.AppStatus.SHUT_DOWN,
                 None,
                 id="ok",
@@ -29,7 +32,9 @@ def lifespan(self):
             pytest.param(
                 Exception("Foo"),
                 None,
-                [call({"type": "lifespan.startup.failed", "message": "Foo"})],
+                [
+                    call({"type": "lifespan.startup.failed", "message": "Foo"}),
+                ],
                 types.AppStatus.FAILED,
                 exceptions.ApplicationError("Lifespan startup failed"),
                 id="fail_before_start",
@@ -64,6 +69,7 @@ async def test_call(
         asgi_scope["app"] = app
         asgi_receive.side_effect = [
             types.Message({"type": "lifespan.startup"}),
+            types.Message({"type": "http"}),
             types.Message({"type": "lifespan.shutdown"}),
         ]
         with exception, patch.object(lifespan, "_startup", side_effect=startup_side_effect), patch.object(
@@ -74,7 +80,15 @@ async def test_call(
         assert asgi_send.call_args_list == send_calls
         assert app._status == app_status
 
-    async def test_startup(self, app, lifespan):
+    @pytest.mark.parametrize(
+        ["child_lifespan"],
+        (
+            pytest.param(MagicMock(), id="lifespan"),
+            pytest.param(None, id="no_lifespan"),
+        ),
+    )
+    async def test_startup(self, app, lifespan, child_lifespan):
+        lifespan.lifespan = child_lifespan
         foo = AsyncMock()
         app.events = MagicMock()
         app.events.startup = [foo]
@@ -82,9 +96,18 @@ async def test_startup(self, app, lifespan):
         await lifespan._startup(app)
 
         assert foo.await_args_list == [call()]
-        assert lifespan.lifespan(app).__aenter__.await_args_list == [call()]
+        if child_lifespan:
+            assert lifespan.lifespan(app).__aenter__.await_args_list == [call()]
 
-    async def test_shutdown(self, app, lifespan):
+    @pytest.mark.parametrize(
+        ["child_lifespan"],
+        (
+            pytest.param(MagicMock(), id="lifespan"),
+            pytest.param(None, id="no_lifespan"),
+        ),
+    )
+    async def test_shutdown(self, app, lifespan, child_lifespan):
+        lifespan.lifespan = child_lifespan
         foo = AsyncMock()
         app.events = MagicMock()
         app.events.shutdown = [foo]
@@ -92,4 +115,5 @@ async def test_shutdown(self, app, lifespan):
         await lifespan._shutdown(app)
 
         assert foo.await_args_list == [call()]
-        assert lifespan.lifespan(app).__aexit__.await_args_list == [call(None, None, None)]
+        if child_lifespan:
+            assert lifespan.lifespan(app).__aexit__.await_args_list == [call(None, None, None)]
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
index 2ffe780e..b3a82429 100644
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -1,9 +1,11 @@
 import typing as t
+from collections import namedtuple
 
 import marshmallow
 import pydantic
 import pytest
 import typesystem
+import typesystem.fields
 from pytest import param
 
 from flama import types
@@ -18,34 +20,35 @@ def app(app):
 
 @pytest.fixture(scope="function")
 def output_schema(app):
-
     if app.schema.schema_library.lib == pydantic:
         schema = pydantic.create_model("OutputSchema", value=(t.Optional[int], ...))
+        name = "pydantic.main.OutputSchema"
     elif app.schema.schema_library.lib == typesystem:
         schema = typesystem.Schema(title="OutputSchema", fields={"value": typesystem.fields.Integer(allow_null=True)})
+        name = "typesystem.schemas.OutputSchema"
     elif app.schema.schema_library.lib == marshmallow:
         schema = type("OutputSchema", (marshmallow.Schema,), {"value": marshmallow.fields.Integer(allow_none=True)})
+        name = "abc.OutputSchema"
     else:
         raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
 
-    app.schema.schemas["OutputSchema"] = schema
-    return schema
+    return namedtuple("OutputSchema", ("schema", "name"))(schema=schema, name=name)
 
 
 class TestCasePageNumberPagination:
     @pytest.fixture(scope="function", autouse=True)
     def add_endpoints(self, app, output_schema):
-        @app.route("/page-number/", methods=["GET"])
-        @paginator.page_number(schema_name="OutputSchema")
-        def page_number(**kwargs) -> types.Schema[output_schema]:
+        @app.route("/page-number/", methods=["GET"], pagination="page_number")
+        def page_number(**kwargs) -> types.Schema[output_schema.schema]:
             return [{"value": i} for i in range(25)]
 
-    def test_registered_schemas(self, app):
+    def test_registered_schemas(self, app, output_schema):
         schemas = app.schema.schema["components"]["schemas"]
+        name_prefix = output_schema.name.rsplit(".", 1)[0]
 
         assert set(schemas.keys()) == {
-            "OutputSchema",
-            "PageNumberPaginatedOutputSchema",
+            f"{name_prefix}.OutputSchema",
+            f"{name_prefix}.PageNumberPaginatedOutputSchema",
             "flama.PageNumberMeta",
             "flama.APIError",
         }
@@ -53,14 +56,14 @@ def test_registered_schemas(self, app):
     def test_invalid_view(self, output_schema):
         with pytest.raises(TypeError, match=r"Paginated views must define \*\*kwargs param"):
 
-            @paginator.page_number(schema_name="OutputSchema")
-            def invalid() -> types.Schema[output_schema]:
+            @paginator._paginate_page_number
+            def invalid() -> types.Schema[output_schema.schema]:
                 ...
 
     def test_invalid_response(self):
         with pytest.raises(ValueError, match=r"Wrong schema type"):
 
-            @paginator.page_number(schema_name="OutputSchema")
+            @paginator._paginate_page_number
             def invalid():
                 ...
 
@@ -82,12 +85,14 @@ def test_pagination_schema_parameters(self, app):
             {"name": "page_size", "in": "query", "required": False, "schema": {"type": ["integer", "null"]}},
         ]
 
-    def test_pagination_schema_return(self, app):
+    def test_pagination_schema_return(self, app, output_schema):
+        prefix, name = output_schema.name.rsplit(".", 1)
+        paginated_output_schema_name = f"{prefix}.PageNumberPaginated{name}"
         response_schema = app.schema.schema["paths"]["/page-number/"]["get"]["responses"]["200"]
-        component_schema = app.schema.schema["components"]["schemas"]["PageNumberPaginatedOutputSchema"]
+        component_schema = app.schema.schema["components"]["schemas"][paginated_output_schema_name]
 
         assert "data" in component_schema["properties"]
-        assert component_schema["properties"]["data"]["items"] == {"$ref": "#/components/schemas/OutputSchema"}
+        assert component_schema["properties"]["data"]["items"] == {"$ref": f"#/components/schemas/{output_schema.name}"}
         assert component_schema["properties"]["data"]["type"] == "array"
         assert set(component_schema["required"]) == {"meta", "data"}
         assert component_schema["type"] == "object"
@@ -98,9 +103,9 @@ def test_pagination_schema_return(self, app):
                 "application/json": {
                     "schema": {
                         "oneOf": [
-                            {"$ref": "#/components/schemas/PageNumberPaginatedOutputSchema"},
+                            {"$ref": f"#/components/schemas/{paginated_output_schema_name}"},
                             {
-                                "items": {"$ref": "#/components/schemas/PageNumberPaginatedOutputSchema"},
+                                "items": {"$ref": f"#/components/schemas/{paginated_output_schema_name}"},
                                 "type": "array",
                             },
                         ]
@@ -110,9 +115,8 @@ def test_pagination_schema_return(self, app):
         }
 
     async def test_async_function(self, app, client, output_schema):
-        @app.route("/page-number-async/", methods=["GET"])
-        @paginator.page_number(schema_name="OutputSchema")
-        async def page_number_async(**kwargs) -> types.Schema[output_schema]:
+        @app.route("/page-number-async/", methods=["GET"], pagination="page_number")
+        async def page_number_async(**kwargs) -> types.Schema[output_schema.schema]:
             return [{"value": i} for i in range(25)]
 
         response = await client.get("/page-number-async/")
@@ -166,17 +170,17 @@ async def test_params(self, client, params, status_code, expected):
 class TestCaseLimitOffsetPagination:
     @pytest.fixture(scope="function", autouse=True)
     def add_endpoints(self, app, output_schema):
-        @app.route("/limit-offset/", methods=["GET"])
-        @paginator.limit_offset(schema_name="OutputSchema")
-        def limit_offset(**kwargs) -> types.Schema[output_schema]:
+        @app.route("/limit-offset/", methods=["GET"], pagination="limit_offset")
+        def limit_offset(**kwargs) -> types.Schema[output_schema.schema]:
             return [{"value": i} for i in range(25)]
 
-    def test_registered_schemas(self, app):
+    def test_registered_schemas(self, app, output_schema):
         schemas = app.schema.schema["components"]["schemas"]
+        name_prefix = output_schema.name.rsplit(".", 1)[0]
 
         assert set(schemas.keys()) == {
-            "OutputSchema",
-            "LimitOffsetPaginatedOutputSchema",
+            f"{name_prefix}.OutputSchema",
+            f"{name_prefix}.LimitOffsetPaginatedOutputSchema",
             "flama.LimitOffsetMeta",
             "flama.APIError",
         }
@@ -184,14 +188,14 @@ def test_registered_schemas(self, app):
     def test_invalid_view(self, output_schema):
         with pytest.raises(TypeError, match=r"Paginated views must define \*\*kwargs param"):
 
-            @paginator.limit_offset(schema_name="Foo")
-            def invalid() -> types.Schema[output_schema]:
+            @paginator._paginate_limit_offset
+            def invalid() -> types.Schema[output_schema.schema]:
                 ...
 
     def test_invalid_response(self):
         with pytest.raises(ValueError, match=r"Wrong schema type"):
 
-            @paginator.limit_offset(schema_name="Foo")
+            @paginator._paginate_limit_offset
             def invalid():
                 ...
 
@@ -213,12 +217,15 @@ def test_pagination_schema_parameters(self, app):
             {"name": "offset", "in": "query", "required": False, "schema": {"type": ["integer", "null"]}},
         ]
 
-    def test_pagination_schema_return(self, app):
+    def test_pagination_schema_return(self, app, output_schema):
+        prefix, name = output_schema.name.rsplit(".", 1)
+        paginated_output_schema_name = f"{prefix}.LimitOffsetPaginated{name}"
+
         response_schema = app.schema.schema["paths"]["/limit-offset/"]["get"]["responses"]["200"]
-        component_schema = app.schema.schema["components"]["schemas"]["LimitOffsetPaginatedOutputSchema"]
+        component_schema = app.schema.schema["components"]["schemas"][paginated_output_schema_name]
 
         assert "data" in component_schema["properties"]
-        assert component_schema["properties"]["data"]["items"] == {"$ref": "#/components/schemas/OutputSchema"}
+        assert component_schema["properties"]["data"]["items"] == {"$ref": f"#/components/schemas/{output_schema.name}"}
         assert component_schema["properties"]["data"]["type"] == "array"
         assert set(component_schema["required"]) == {"meta", "data"}
         assert component_schema["type"] == "object"
@@ -229,9 +236,9 @@ def test_pagination_schema_return(self, app):
                 "application/json": {
                     "schema": {
                         "oneOf": [
-                            {"$ref": "#/components/schemas/LimitOffsetPaginatedOutputSchema"},
+                            {"$ref": f"#/components/schemas/{paginated_output_schema_name}"},
                             {
-                                "items": {"$ref": "#/components/schemas/LimitOffsetPaginatedOutputSchema"},
+                                "items": {"$ref": f"#/components/schemas/{paginated_output_schema_name}"},
                                 "type": "array",
                             },
                         ]
@@ -241,9 +248,8 @@ def test_pagination_schema_return(self, app):
         }
 
     async def test_async_function(self, app, client, output_schema):
-        @app.route("/limit-offset-async/", methods=["GET"])
-        @paginator.limit_offset(schema_name="OutputSchema")
-        async def limit_offset_async(**kwargs) -> types.Schema[output_schema]:
+        @app.route("/limit-offset-async/", methods=["GET"], pagination="limit_offset")
+        async def limit_offset_async(**kwargs) -> types.Schema[output_schema.schema]:
             return [{"value": i} for i in range(25)]
 
         response = await client.get("/limit-offset-async/")
diff --git a/tests/test_routing.py b/tests/test_routing.py
index 9d326239..5a3552e0 100644
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -351,14 +351,24 @@ def test_eq(self, app):
         assert Mount("/", app, name="app_mock") == Mount("/", app, name="app_mock")
         assert Mount("/", app, name="app_mock") != Mount("/", app, name="bar")
 
-    def test_build(self, mount, app):
+    @pytest.mark.parametrize(
+        ["app", "used"],
+        (
+            pytest.param(MagicMock(spec=Router), False, id="router"),
+            pytest.param(MagicMock(spec=Flama, router=MagicMock(spec=Router, components=[])), True, id="app"),
+        ),
+    )
+    def test_build(self, mount, app, used):
+        root_app = MagicMock(spec=Flama)
+        expected_calls = [call(app)] if used else [call(root_app)]
+
         route = MagicMock(spec=Route)
-        mount.app = MagicMock(spec=Flama)
+        mount.app = app
         mount.app.routes = [route]
 
-        mount.build(app)
+        mount.build(root_app)
 
-        assert route.build.call_args_list == [call(app)]
+        assert route.build.call_args_list == expected_calls
 
     @pytest.mark.parametrize(
         ["scope_type", "path_match_return", "result"],
@@ -383,16 +393,30 @@ async def test_handle(self, mount, asgi_scope, asgi_receive, asgi_send):
 
         assert mount.app.call_args_list == [call(asgi_scope, asgi_receive, asgi_send)]
 
-    def test_route_scope(self, mount, asgi_scope):
+    @pytest.mark.parametrize(
+        ["app", "used"],
+        (
+            pytest.param(Router(), False, id="router"),
+            pytest.param(Flama(docs=None, schema=None), True, id="app"),
+        ),
+    )
+    def test_route_scope(self, mount, asgi_scope, app, used):
         def bar():
             ...
 
+        mount.app = app
         mount.app.add_route("/bar", bar)
 
         asgi_scope["path"] = "/foo/1/bar"
         route_scope = mount.route_scope(asgi_scope)
 
-        assert route_scope == {"endpoint": mount.app, "path": "/bar", "path_params": {"x": 1}, "root_path": "/foo/1"}
+        assert route_scope == {
+            "app": app if used else asgi_scope["app"],
+            "endpoint": mount.app,
+            "path": "/bar",
+            "path_params": {"x": 1},
+            "root_path": "/foo/1",
+        }
 
     @pytest.mark.parametrize(
         ["name", "params", "expected_url", "exception"],
@@ -452,7 +476,7 @@ def router(self, app):
 
     @pytest.fixture(scope="function")
     def app_mock(self):
-        return MagicMock(spec=Flama)
+        return MagicMock(spec=Flama, router=MagicMock(spec=Router, components=Components([])))
 
     @pytest.fixture(scope="function")
     def component_mock(self):
@@ -527,9 +551,14 @@ def test_build(self, router):
     def test_components(self, router, component_mock):
         assert router.components == []
 
-        router.mount("/app/", app=Router(components=[component_mock]))
+        router.add_component(component_mock)
+        leaf_component = MagicMock(spec=Component)
+        leaf_router = Router(components=[leaf_component])
+        router.mount("/app/", app=leaf_router)
 
         assert router.components == [component_mock]
+        # Components are not propagated to leaf because mounted was done through a router instead of app
+        assert leaf_router.components == [leaf_component]
 
     def test_add_component(self, router, component_mock):
         assert router.components == []
@@ -693,8 +722,9 @@ def test_mount_router(self, app, component_mock, tags):
         # Check router is created and initialized, also shares components and modules with main app
         assert isinstance(mount_route.app, Router)
         mount_router = mount_route.app
-        assert mount_router.components == Components([component_mock])
-        assert app.components == Components([component_mock])
+        default_components = app.components[:1]
+        assert mount_router.components == [component_mock]
+        assert app.components == default_components
 
     def test_mount_declarative(self, component_mock, tags):
         def root():
@@ -742,9 +772,9 @@ def foo_view():
         # Check router is created and initialized, also shares components and modules with main app
         assert isinstance(mount_with_routes_route.app, Router)
         mount_with_routes_router = mount_with_routes_route.app
-        assert mount_with_routes_router.components == Components([component_mock])
-        # As the component is repeated, it should appear twice
-        assert app.components == Components([component_mock, component_mock])
+        assert mount_with_routes_router.components == [component_mock]
+        default_components = app.components[:1]
+        assert app.components == default_components
         # Check second-level routes are created an initialized
         assert len(mount_with_routes_route.routes) == 2
         assert mount_with_routes_route.routes[0].path == "/"
@@ -758,9 +788,8 @@ def foo_view():
         # Check router is created and initialized, also shares components and modules with main app
         assert isinstance(mount_with_app_route.app, Router)
         mount_with_app_router = mount_with_app_route.app
-        assert mount_with_app_router.components == Components([component_mock])
-        # As the component is repeated, it should appear twice
-        assert app.components == Components([component_mock, component_mock])
+        assert mount_with_app_router.components == [component_mock]
+        assert app.components == default_components
         # Check second-level routes are created an initialized
         assert len(mount_with_app_route.routes) == 2
         assert mount_with_app_route.routes[0].path == "/"
diff --git a/tests/utils.py b/tests/utils.py
index ce8441c7..49884984 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,6 +1,10 @@
 import importlib.util
 import sys
-import typing
+import typing as t
+
+import sqlalchemy
+
+from flama import Flama
 
 
 def installed(module: str) -> bool:
@@ -13,7 +17,7 @@ def installed(module: str) -> bool:
 
 
 class ExceptionContext:
-    def __init__(self, context, exception: typing.Optional[Exception] = None):
+    def __init__(self, context, exception: t.Optional[Exception] = None):
         self.context = context
         self.exception = exception
 
@@ -25,3 +29,17 @@ def __exit__(self, exc_type, exc_val, exc_tb):
 
     def __bool__(self) -> bool:
         return self.exception is not None
+
+
+class SQLAlchemyContext:
+    def __init__(self, app: Flama, tables: t.List[sqlalchemy.Table]):
+        self.app = app
+        self.tables = tables
+
+    async def __aenter__(self):
+        async with self.app.sqlalchemy.engine.begin() as connection:
+            await connection.run_sync(self.app.sqlalchemy.metadata.create_all, tables=self.tables)
+
+    async def __aexit__(self, exc_type, exc_val, exc_tb):
+        async with self.app.sqlalchemy.engine.begin() as connection:
+            await connection.run_sync(self.app.sqlalchemy.metadata.drop_all, tables=self.tables)
diff --git a/tests/validation/test_data.py b/tests/validation/test_data.py
index 7f13d47a..7bacc99e 100644
--- a/tests/validation/test_data.py
+++ b/tests/validation/test_data.py
@@ -2,20 +2,9 @@
 from _pytest.mark import param
 
 from flama import endpoints, types, websockets
-from flama.applications import Flama
-from flama.client import AsyncClient
 
 
 class TestCaseDataValidation:
-    @pytest.fixture(scope="function")
-    def app(self):
-        return Flama(schema=None, docs=None)
-
-    @pytest.fixture(scope="function")
-    async def client(self, app):
-        async with AsyncClient(app=app) as client:
-            yield client
-
     @pytest.mark.parametrize(
         "request_params,response_status,response_json",
         [
diff --git a/tests/validation/test_params.py b/tests/validation/test_params.py
index db37d286..57a442fe 100644
--- a/tests/validation/test_params.py
+++ b/tests/validation/test_params.py
@@ -9,148 +9,148 @@
 class TestCaseParamsValidation:
     @pytest.fixture(scope="function", autouse=True)
     def add_path_endpoints(self, app):
-        @app.route("/str_path_param/{param}/")
+        @app.route("/str-path-param/{param}/")
         def str_path_param(param: str):
             assert isinstance(param, str)
             return {"param": param}
 
-        @app.route("/int_path_param/{param}/")
+        @app.route("/int-path-param/{param}/")
         def int_path_param(param: int):
             assert isinstance(param, int)
             return {"param": param}
 
-        @app.route("/float_path_param/{param}/")
+        @app.route("/float-path-param/{param}/")
         def float_path_param(param: float):
             assert isinstance(param, float)
             return {"param": param}
 
-        @app.route("/bool_path_param/{param}/")
+        @app.route("/bool-path-param/{param}/")
         def bool_path_param(param: bool):
             assert isinstance(param, bool)
             return {"param": param}
 
-        @app.route("/datetime_path_param/{param}/")
+        @app.route("/datetime-path-param/{param}/")
         def datetime_path_param(param: datetime.datetime):
             assert isinstance(param, datetime.datetime)
             return {"param": param}
 
-        @app.route("/date_path_param/{param}/")
+        @app.route("/date-path-param/{param}/")
         def date_path_param(param: datetime.date):
             assert isinstance(param, datetime.date)
             return {"param": param}
 
-        @app.route("/time_path_param/{param}/")
+        @app.route("/time-path-param/{param}/")
         def time_path_param(param: datetime.time):
             assert isinstance(param, datetime.time)
             return {"param": param}
 
     @pytest.fixture(scope="function", autouse=True)
     def add_query_endpoints(self, app):
-        @app.route("/str_query_param/")
+        @app.route("/str-query-param/")
         def str_query_param(param: str):
             assert isinstance(param, str)
             return {"param": param}
 
-        @app.route("/int_query_param/")
+        @app.route("/int-query-param/")
         def int_query_param(param: int):
             assert isinstance(param, int)
             return {"param": param}
 
-        @app.route("/float_query_param/")
+        @app.route("/float-query-param/")
         def float_query_param(param: float):
             assert isinstance(param, float)
             return {"param": param}
 
-        @app.route("/bool_query_param/")
+        @app.route("/bool-query-param/")
         def bool_query_param(param: bool):
             assert isinstance(param, bool)
             return {"param": param}
 
-        @app.route("/datetime_query_param/")
+        @app.route("/datetime-query-param/")
         def datetime_query_param(param: datetime.datetime):
             assert isinstance(param, datetime.datetime)
             return {"param": param}
 
-        @app.route("/date_query_param/")
+        @app.route("/date-query-param/")
         def date_query_param(param: datetime.date):
             assert isinstance(param, datetime.date)
             return {"param": param}
 
-        @app.route("/time_query_param/")
+        @app.route("/time-query-param/")
         def time_query_param(param: datetime.time):
             assert isinstance(param, datetime.time)
             return {"param": param}
 
     @pytest.fixture(scope="function", autouse=True)
     def add_query_with_default_endpoints(self, app):
-        @app.route("/str_query_param_with_default/")
+        @app.route("/str-query-param-with_default/")
         def str_query_param_with_default(param: str = "Foo"):
             assert isinstance(param, str)
             return {"param": param}
 
-        @app.route("/int_query_param_with_default/")
+        @app.route("/int-query-param-with_default/")
         def int_query_param_with_default(param: int = 0):
             assert isinstance(param, int)
             return {"param": param}
 
-        @app.route("/float_query_param_with_default/")
+        @app.route("/float-query-param-with_default/")
         def float_query_param_with_default(param: float = 0.0):
             assert isinstance(param, float)
             return {"param": param}
 
-        @app.route("/bool_query_param_with_default/")
+        @app.route("/bool-query-param-with_default/")
         def bool_query_param_with_default(param: bool = False):
             assert isinstance(param, bool)
             return {"param": param}
 
-        @app.route("/datetime_query_param_with_default/")
+        @app.route("/datetime-query-param-with_default/")
         def datetime_query_param_with_default(param: datetime.datetime = datetime.datetime(2018, 1, 1, 0, 0, 0)):
             assert isinstance(param, datetime.datetime)
             return {"param": param}
 
-        @app.route("/date_query_param_with_default/")
+        @app.route("/date-query-param-with_default/")
         def date_query_param_with_default(param: datetime.date = datetime.date(2018, 1, 1)):
             assert isinstance(param, datetime.date)
             return {"param": param}
 
-        @app.route("/time_query_param_with_default/")
+        @app.route("/time-query-param-with_default/")
         def time_query_param_with_default(param: datetime.time = datetime.time(0, 0, 0)):
             assert isinstance(param, datetime.time)
             return {"param": param}
 
     @pytest.fixture(scope="function", autouse=True)
     def add_query_optional_endpoints(self, app):
-        @app.route("/str_query_param_optional/")
+        @app.route("/str-query-param-optional/")
         def str_query_param_optional(param: typing.Optional[str] = None):
             assert param is None
             return {"param": param}
 
-        @app.route("/int_query_param_optional/")
+        @app.route("/int-query-param-optional/")
         def int_query_param_optional(param: typing.Optional[int] = None):
             assert param is None
             return {"param": param}
 
-        @app.route("/float_query_param_optional/")
+        @app.route("/float-query-param-optional/")
         def float_query_param_optional(param: typing.Optional[float] = None):
             assert param is None
             return {"param": param}
 
-        @app.route("/bool_query_param_optional/")
+        @app.route("/bool-query-param-optional/")
         def bool_query_param_optional(param: typing.Optional[bool] = None):
             assert param is None
             return {"param": param}
 
-        @app.route("/datetime_query_param_optional/")
+        @app.route("/datetime-query-param-optional/")
         def datetime_query_param_optional(param: typing.Optional[datetime.datetime] = None):
             assert param is None
             return {"param": param}
 
-        @app.route("/date_query_param_optional/")
+        @app.route("/date-query-param-optional/")
         def date_query_param_optional(param: typing.Optional[datetime.date] = None):
             assert param is None
             return {"param": param}
 
-        @app.route("/time_query_param_optional/")
+        @app.route("/time-query-param-optional/")
         def time_query_param_optional(param: typing.Optional[datetime.time] = None):
             assert param is None
             return {"param": param}
@@ -162,17 +162,17 @@ def empty(foo):
             return {}
 
     @pytest.mark.parametrize(
-        "url,value",
+        ["url", "value"],
         [
-            pytest.param("/str_path_param/123/", "123", id="str path param"),
-            pytest.param("/int_path_param/123/", 123, id="int path param"),
-            pytest.param("/float_path_param/123.321/", 123.321, id="float path param"),
-            pytest.param("/bool_path_param/true/", True, id="float path param"),
+            pytest.param("/str-path-param/123/", "123", id="str-path-param"),
+            pytest.param("/int-path-param/123/", 123, id="int-path-param"),
+            pytest.param("/float-path-param/123.321/", 123.321, id="float-path-param"),
+            pytest.param("/bool-path-param/true/", True, id="float-path-param"),
             pytest.param(
-                "/datetime_path_param/2018-01-01T00:00:00+00:00/", "2018-01-01T00:00:00+00:00", id="datetime path param"
+                "/datetime-path-param/2018-01-01T00:00:00+00:00/", "2018-01-01T00:00:00+00:00", id="datetime-path-param"
             ),
-            pytest.param("/date_path_param/2018-01-01/", "2018-01-01", id="date path param"),
-            pytest.param("/time_path_param/00:00:00/", "00:00:00", id="time path param"),
+            pytest.param("/date-path-param/2018-01-01/", "2018-01-01", id="date-path-param"),
+            pytest.param("/time-path-param/00:00:00/", "00:00:00", id="time-path-param"),
         ],
     )
     async def test_path_param(self, url, value, client):
@@ -180,15 +180,15 @@ async def test_path_param(self, url, value, client):
         assert response.json() == {"param": value}
 
     @pytest.mark.parametrize(
-        "url,value",
+        ["url", "value"],
         [
-            pytest.param("/str_query_param/", "123", id="str query param"),
-            pytest.param("/int_query_param/", 123, id="int query param"),
-            pytest.param("/float_query_param/", 123.321, id="float query param"),
-            pytest.param("/bool_query_param/", True, id="bool query param"),
-            pytest.param("/datetime_query_param/", "2018-01-01T00:00:00", id="datetime query param"),
-            pytest.param("/date_query_param/", "2018-01-01", id="date query param"),
-            pytest.param("/time_query_param/", "00:00:00", id="time query param"),
+            pytest.param("/str-query-param/", "123", id="str-query-param"),
+            pytest.param("/int-query-param/", 123, id="int-query-param"),
+            pytest.param("/float-query-param/", 123.321, id="float-query-param"),
+            pytest.param("/bool-query-param/", True, id="bool-query-param"),
+            pytest.param("/datetime-query-param/", "2018-01-01T00:00:00", id="datetime-query-param"),
+            pytest.param("/date-query-param/", "2018-01-01", id="date-query-param"),
+            pytest.param("/time-query-param/", "00:00:00", id="time-query-param"),
         ],
     )
     async def test_query_param(self, url, value, client):
@@ -196,17 +196,17 @@ async def test_query_param(self, url, value, client):
         assert response.json() == {"param": value}
 
     @pytest.mark.parametrize(
-        "url,value",
+        ["url", "value"],
         [
-            pytest.param("/str_query_param_with_default/", "Foo", id="str query param with default"),
-            pytest.param("/int_query_param_with_default/", 0, id="int query param with default"),
-            pytest.param("/float_query_param_with_default/", 0.0, id="float query param with default"),
-            pytest.param("/bool_query_param_with_default/", False, id="bool query param with default"),
+            pytest.param("/str-query-param-with_default/", "Foo", id="str-query-param-with-default"),
+            pytest.param("/int-query-param-with_default/", 0, id="int-query-param-with-default"),
+            pytest.param("/float-query-param-with_default/", 0.0, id="float-query-param-with-default"),
+            pytest.param("/bool-query-param-with_default/", False, id="bool-query-param-with-default"),
             pytest.param(
-                "/datetime_query_param_with_default/", "2018-01-01T00:00:00", id="datetime query param with default"
+                "/datetime-query-param-with_default/", "2018-01-01T00:00:00", id="datetime-query-param-with-default"
             ),
-            pytest.param("/date_query_param_with_default/", "2018-01-01", id="date query param with default"),
-            pytest.param("/time_query_param_with_default/", "00:00:00", id="time query param with default"),
+            pytest.param("/date-query-param-with_default/", "2018-01-01", id="date-query-param-with-default"),
+            pytest.param("/time-query-param-with_default/", "00:00:00", id="time-query-param-with-default"),
         ],
     )
     async def test_query_param_with_default(self, url, value, client):
@@ -216,13 +216,13 @@ async def test_query_param_with_default(self, url, value, client):
     @pytest.mark.parametrize(
         "url",
         [
-            pytest.param("/str_query_param_optional/", id="str query param optional"),
-            pytest.param("/int_query_param_optional/", id="int query param optional"),
-            pytest.param("/float_query_param_optional/", id="float query param optional"),
-            pytest.param("/bool_query_param_optional/", id="bool query param optional"),
-            pytest.param("/datetime_query_param_optional/", id="datetime query param optional"),
-            pytest.param("/date_query_param_optional/", id="date query param optional"),
-            pytest.param("/time_query_param_optional/", id="time query param optional"),
+            pytest.param("/str-query-param-optional/", id="str-query-param-optional"),
+            pytest.param("/int-query-param-optional/", id="int-query-param-optional"),
+            pytest.param("/float-query-param-optional/", id="float-query-param-optional"),
+            pytest.param("/bool-query-param-optional/", id="bool-query-param-optional"),
+            pytest.param("/datetime-query-param-optional/", id="datetime-query-param-optional"),
+            pytest.param("/date-query-param-optional/", id="date-query-param-optional"),
+            pytest.param("/time-query-param-optional/", id="time-query-param-optional"),
         ],
     )
     async def test_query_param_optional(self, url, client):
@@ -230,11 +230,11 @@ async def test_query_param_optional(self, url, client):
         assert response.json() == {"param": None}
 
     async def test_wrong_query_param(self, client):
-        response = await client.get("/int_query_param/?param=foo")
+        response = await client.get("/int-query-param/?param=foo")
         assert response.status_code == 400
 
     async def test_wrong_path_param(self, client):
-        response = await client.get("/int_path_param/foo/")
+        response = await client.get("/int-path-param/foo/")
         assert response.status_code == 400
 
     async def test_no_type_param(self, client):
diff --git a/tests/validation/test_return.py b/tests/validation/test_return.py
index 88fe7d4b..220e31db 100644
--- a/tests/validation/test_return.py
+++ b/tests/validation/test_return.py
@@ -1,54 +1,55 @@
 import marshmallow
+import pydantic
 import pytest
+import typesystem
+import typesystem.fields
 
-from flama import Component, endpoints, http, types
-from flama.applications import Flama
-from flama.client import AsyncClient
+from flama import endpoints, http, types
 
 
-class Puppy:
-    name = "Canna"
-
-
-class PuppyComponent(Component):
-    def resolve(self) -> Puppy:
-        return Puppy()
-
-
-class BodyParam(marshmallow.Schema):
-    name = marshmallow.fields.String()
+class TestCaseReturnValidation:
+    @pytest.fixture(scope="function")
+    def output_schema(self, app):
+        if app.schema.schema_library.lib == pydantic:
+            schema = pydantic.create_model("OutputSchema", name=(str, ...))
+        elif app.schema.schema_library.lib == typesystem:
+            schema = typesystem.Schema(title="OutputSchema", fields={"name": typesystem.fields.String()})
+        elif app.schema.schema_library.lib == marshmallow:
+            schema = type("OutputSchema", (marshmallow.Schema,), {"name": marshmallow.fields.String()})
+        else:
+            raise ValueError(f"Wrong schema lib: {app.schema.schema_library.lib}")
 
+        return schema
 
-class TestCaseReturnValidation:
     @pytest.fixture(  # noqa: C901
-        scope="class", params=[pytest.param(True, id="endpoints"), pytest.param(False, id="function views")]
+        scope="function",
+        params=[pytest.param(True, id="endpoints"), pytest.param(False, id="function views")],
+        autouse=True,
     )
-    def app(self, request):  # noqa: C901
-        app_ = Flama(components=[PuppyComponent()], schema=None, docs=None)
-
+    def add_endpoints(self, app, request, output_schema):  # noqa: C901
         if request.param:
 
-            @app_.route("/return_string/")
+            @app.route("/return-string/")
             class StrEndpoint(endpoints.HTTPEndpoint):
                 def get(self, data: types.RequestData) -> str:
                     return "example content"
 
-            @app_.route("/return_html/")
-            class HTMLEndpoint(endpoints.HTTPEndpoint):
-                def get(self, data: types.RequestData) -> http.HTMLResponse:
-                    return http.HTMLResponse("<html><body>example content</body></html>")
-
-            @app_.route("/return_data/")
+            @app.route("/return-dict/")
             class DictEndpoint(endpoints.HTTPEndpoint):
                 def get(self, data: types.RequestData) -> dict:
                     return {"example": "content"}
 
-            @app_.route("/return_response/")
+            @app.route("/return-html-response/")
+            class HTMLEndpoint(endpoints.HTTPEndpoint):
+                def get(self, data: types.RequestData) -> http.HTMLResponse:
+                    return http.HTMLResponse("<html><body>example content</body></html>")
+
+            @app.route("/return-json-response/")
             class JSONEndpoint(endpoints.HTTPEndpoint):
                 def get(self, data: types.RequestData) -> http.JSONResponse:
                     return http.JSONResponse({"example": "content"})
 
-            @app_.route("/return_unserializable_json/")
+            @app.route("/return-unserializable-json/")
             class UnserializableEndpoint(endpoints.HTTPEndpoint):
                 def get(self) -> dict:
                     class Dummy:
@@ -56,112 +57,99 @@ class Dummy:
 
                     return {"dummy": Dummy()}
 
-            @app_.route("/return-schema/", methods=["GET"])
+            @app.route("/return-schema/", methods=["GET"])
             class ReturnSchemaHTTPEndpoint(endpoints.HTTPEndpoint):
-                async def get(self) -> BodyParam:
+                async def get(self) -> types.Schema[output_schema]:
                     return {"name": "Canna"}
 
-            @app_.route("/return-schema-many/", methods=["GET"])
+            @app.route("/return-schema-many/", methods=["GET"])
             class ReturnSchemaManyHTTPEndpoint(endpoints.HTTPEndpoint):
-                async def get(self) -> BodyParam(many=True):
+                async def get(self) -> types.Schema[output_schema]:
                     return [{"name": "Canna"}, {"name": "Sandy"}]
 
-            @app_.route("/return-schema-empty/", methods=["GET"])
+            @app.route("/return-schema-empty/", methods=["GET"])
             class ReturnSchemaEmptyHTTPEndpoint(endpoints.HTTPEndpoint):
-                async def get(self) -> BodyParam:
+                async def get(self) -> types.Schema[output_schema]:
                     return None
 
         else:
 
-            @app_.route("/return_string/")
+            @app.route("/return-string/")
             def return_string(data: types.RequestData) -> str:
                 return "example content"
 
-            @app_.route("/return_html/")
-            def return_html(data: types.RequestData) -> http.HTMLResponse:
-                return http.HTMLResponse("<html><body>example content</body></html>")
-
-            @app_.route("/return_data/")
+            @app.route("/return-dict/")
             def return_data(data: types.RequestData) -> dict:
                 return {"example": "content"}
 
-            @app_.route("/return_response/")
+            @app.route("/return-html-response/")
+            def return_html(data: types.RequestData) -> http.HTMLResponse:
+                return http.HTMLResponse("<html><body>example content</body></html>")
+
+            @app.route("/return-json-response/")
             def return_response(data: types.RequestData) -> http.Response:
                 return http.JSONResponse({"example": "content"})
 
-            @app_.route("/return_unserializable_json/")
+            @app.route("/return-unserializable-json/")
             def return_unserializable_json() -> dict:
                 class Dummy:
                     pass
 
                 return {"dummy": Dummy()}
 
-            @app_.route("/return-schema/", methods=["GET"])
-            async def return_schema() -> BodyParam:
+            @app.route("/return-schema/", methods=["GET"])
+            async def return_schema() -> types.Schema[output_schema]:
                 return {"name": "Canna"}
 
-            @app_.route("/return-schema-many/", methods=["GET"])
-            async def return_schema_many() -> BodyParam(many=True):
+            @app.route("/return-schema-many/", methods=["GET"])
+            async def return_schema_many() -> types.Schema[output_schema]:
                 return [{"name": "Canna"}, {"name": "Sandy"}]
 
-            @app_.route("/return-schema-empty/", methods=["GET"])
-            async def return_schema_empty() -> BodyParam:
+            @app.route("/return-schema-empty/", methods=["GET"])
+            async def return_schema_empty() -> types.Schema[output_schema]:
                 return None
 
-        return app_
-
-    @pytest.fixture(scope="function")
-    async def client(self, app):
-        async with AsyncClient(app=app) as client:
-            yield client
-
-    async def test_return_string(self, client):
-        response = await client.get("/return_string/")
-
-        assert response.status_code == 200
-        assert response.headers["content-type"] == "application/json"
-        assert response.json() == "example content"
-
-    async def test_return_html(self, client):
-        response = await client.get("/return_html/")
-        assert response.headers["content-type"] == "text/html; charset=utf-8"
-        assert response.text == "<html><body>example content</body></html>"
-
-    async def test_return_data(self, client):
-        response = await client.get("/return_data/")
-
-        assert response.status_code == 200
-        assert response.headers["content-type"] == "application/json"
-        assert response.json() == {"example": "content"}
-
-    async def test_return_response(self, client):
-        response = await client.get("/return_response/")
-
-        assert response.status_code == 200
-        assert response.headers["content-type"] == "application/json"
-        assert response.json() == {"example": "content"}
-
-    async def test_return_unserializable_json(self, client):
-        with pytest.raises(TypeError, match=r".*Object of type .?Dummy.? is not JSON serializable"):
-            await client.get("/return_unserializable_json/")
-
-    async def test_return_schema(self, client):
-        response = await client.get("/return-schema/")
-
-        assert response.status_code == 200
-        assert response.headers["content-type"] == "application/json"
-        assert response.json() == {"name": "Canna"}
-
-    async def test_return_schema_many(self, client):
-        response = await client.get("/return-schema-many/")
-
-        assert response.status_code == 200
-        assert response.headers["content-type"] == "application/json"
-        assert response.json() == [{"name": "Canna"}, {"name": "Sandy"}]
-
-    async def test_return_schema_empty(self, client):
-        response = await client.get("/return-schema-empty/")
+    @pytest.mark.parametrize(
+        ["path", "status_code", "content_type", "content", "exception"],
+        (
+            pytest.param("/return-string/", 200, "application/json", b'"example content"', None, id="string"),
+            pytest.param("/return-dict/", 200, "application/json", b'{"example":"content"}', None, id="dict"),
+            pytest.param(
+                "/return-html-response/",
+                200,
+                "text/html; charset=utf-8",
+                b"<html><body>example content</body></html>",
+                None,
+                id="html_response",
+            ),
+            pytest.param(
+                "/return-json-response/", 200, "application/json", b'{"example":"content"}', None, id="json_response"
+            ),
+            pytest.param(
+                "/return-unserializable-json/",
+                None,
+                None,
+                None,
+                TypeError(".*Object of type .?Dummy.? is not JSON serializable"),
+                id="unserializable_json_response",
+            ),
+            pytest.param("/return-schema/", 200, "application/json", b'{"name":"Canna"}', None, id="schema"),
+            pytest.param(
+                "/return-schema-many/",
+                200,
+                "application/json",
+                b'[{"name":"Canna"},{"name":"Sandy"}]',
+                None,
+                id="schema_many",
+            ),
+            pytest.param("/return-schema-empty/", 200, "application/json", b"", None, id="schema_empty"),
+        ),
+        indirect=["exception"],
+    )
+    async def test_return(self, client, path, status_code, content_type, content, exception):
+        with exception:
+            response = await client.get(path)
 
-        assert response.status_code == 200
-        assert response.headers["content-type"] == "application/json"
-        assert response.content == b""
+            assert response.status_code == status_code
+            assert response.headers["content-type"] == content_type
+            assert response.content == content
diff --git a/tests/validation/test_schemas.py b/tests/validation/test_schemas.py
index fce9c6b7..7c9fed5a 100644
--- a/tests/validation/test_schemas.py
+++ b/tests/validation/test_schemas.py
@@ -6,6 +6,7 @@
 import pydantic
 import pytest
 import typesystem
+import typesystem.fields
 
 from flama import types
 from tests.asserts import assert_recursive_contains
@@ -16,13 +17,11 @@
 class TestCaseSchemaValidation:
     @pytest.fixture(scope="function")
     def product_schema(self, app):
-        from flama import schemas
-
-        if schemas.lib == pydantic:
+        if app.schema.schema_library.lib == pydantic:
             schema = pydantic.create_model(
                 "Product", name=(str, ...), rating=(t.Optional[int], ...), created=(t.Optional[datetime.datetime], ...)
             )
-        elif schemas.lib == typesystem:
+        elif app.schema.schema_library.lib == typesystem:
             schema = typesystem.Schema(
                 title="Product",
                 fields={
@@ -31,7 +30,7 @@ def product_schema(self, app):
                     "created": typesystem.fields.DateTime(allow_null=True),
                 },
             )
-        elif schemas.lib == marshmallow:
+        elif app.schema.schema_library.lib == marshmallow:
             schema = type(
                 "Product",
                 (marshmallow.Schema,),
@@ -44,32 +43,26 @@ def product_schema(self, app):
         else:
             raise ValueError("Wrong schema lib")
 
-        app.schema.register_schema("Product", schema)
         return schema
 
     @pytest.fixture(scope="function")
     def reviewed_product_schema(self, app, product_schema):
-        from flama import schemas
-
-        if schemas.lib == pydantic:
+        if app.schema.schema_library.lib == pydantic:
             schema = pydantic.create_model("ReviewedProduct", reviewer=(str, ...), __base__=product_schema)
-        elif schemas.lib == typesystem:
+        elif app.schema.schema_library.lib == typesystem:
             schema = typesystem.Schema(
                 title="ReviewedProduct", fields={**product_schema.fields, **{"reviewer": typesystem.fields.String()}}
             )
-        elif schemas.lib == marshmallow:
+        elif app.schema.schema_library.lib == marshmallow:
             schema = type("ReviewedProduct", (product_schema,), {"reviewer": marshmallow.fields.String()})
         else:
             raise ValueError("Wrong schema lib")
 
-        app.schema.schemas["ReviewedProduct"] = schema
         return schema
 
     @pytest.fixture(scope="function")
     def location_schema(self, app):
-        from flama import schemas
-
-        if schemas.lib == pydantic:
+        if app.schema.schema_library.lib == pydantic:
 
             def latitude_validator(cls, x):
                 assert -90 <= x <= 90
@@ -85,14 +78,14 @@ def longitude_validator(cls, x):
                 longitude=(float, ...),
                 __validators__={"latitude": latitude_validator, "longitude": longitude_validator},
             )
-        elif schemas.lib == typesystem:
+        elif app.schema.schema_library.lib == typesystem:
             schema = typesystem.Schema(
                 fields={
                     "latitude": typesystem.fields.Number(minimum=-90, maximum=90),
                     "longitude": typesystem.fields.Number(minimum=-180, maximum=180),
                 }
             )
-        elif schemas.lib == marshmallow:
+        elif app.schema.schema_library.lib == marshmallow:
             schema = type(
                 "Location",
                 (marshmallow.Schema,),
@@ -103,24 +96,20 @@ def longitude_validator(cls, x):
             )
         else:
             raise ValueError("Wrong schema lib")
-
-        app.schema.schemas["Location"] = schema
         return schema
 
     @pytest.fixture(scope="function")
     def place_schema(self, app, location_schema):
-        from flama import schemas
-
-        if schemas.lib == pydantic:
+        if app.schema.schema_library.lib == pydantic:
             schema = pydantic.create_model("Place", location=(location_schema, ...), name=(str, ...))
-        elif schemas.lib == typesystem:
+        elif app.schema.schema_library.lib == typesystem:
             schema = typesystem.Schema(
                 fields={
-                    "location": typesystem.Reference("Location", app.schema.schemas),
+                    "location": typesystem.Reference("Location", typesystem.Definitions({"Location": location_schema})),
                     "name": typesystem.String(),
                 }
             )
-        elif schemas.lib == marshmallow:
+        elif app.schema.schema_library.lib == marshmallow:
             schema = type(
                 "Place",
                 (marshmallow.Schema,),
@@ -128,11 +117,10 @@ def place_schema(self, app, location_schema):
             )
         else:
             raise ValueError("Wrong schema lib")
-        app.schema.schemas["Place"] = schema
         return schema
 
     @pytest.fixture(scope="function", autouse=True)
-    def add_endpoints(self, app, product_schema, reviewed_product_schema, location_schema, place_schema):
+    def add_endpoints(self, app, product_schema, reviewed_product_schema, place_schema):
         @app.route("/product", methods=["POST"])
         def product_identity(product: types.Schema[product_schema]) -> types.Schema[product_schema]:
             return product
@@ -167,7 +155,7 @@ def serialization_error() -> types.Schema[product_schema]:
             return {"rating": "foo", "created": "bar"}
 
     @pytest.mark.parametrize(
-        "path,method,test_input,expected_output,status_code",
+        ["path", "method", "json_data", "expected_output", "status_code"],
         [
             pytest.param(
                 "/product",
@@ -243,7 +231,7 @@ def serialization_error() -> types.Schema[product_schema]:
             ),
         ],
     )
-    async def test_schemas(self, client, path, method, test_input, expected_output, status_code):
-        response = await client.request(method, path, json=test_input)
+    async def test_schemas(self, client, path, method, json_data, expected_output, status_code):
+        response = await client.request(method, path, json=json_data)
         assert response.status_code == status_code, response.json()
         assert_recursive_contains(expected_output, response.json())