Repository: reagento/adaptix (language: python)

Issue:
Better error msg



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index fd807d4d..4b9a104d 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ An extremely flexible and configurable data model conversion library.
 
 Install
 ```bash
-pip install adaptix==3.0.0b7
+pip install adaptix==3.0.0b8
 ```
 
 Use for model loading and dumping.
diff --git a/benchmarks/benchmarks/bench_nexus.py b/benchmarks/benchmarks/bench_nexus.py
index ec2a93f2..55f092b7 100644
--- a/benchmarks/benchmarks/bench_nexus.py
+++ b/benchmarks/benchmarks/bench_nexus.py
@@ -267,7 +267,7 @@ def filtered_hubs(self) -> Iterable[HubDescription]:
         return BENCHMARK_HUBS
 
     def filtered_envs(self) -> Iterable[EnvDescription]:
-        if self.env_include:
+        if self.env_include and self.env_exclude:
             wild_envs = set(self.env_exclude) - {env_description.key for env_description in BENCHMARK_ENVS}
             if wild_envs:
                 raise ValueError(f"Unknown envs {wild_envs}")
diff --git a/benchmarks/benchmarks/pybench/bench_api.py b/benchmarks/benchmarks/pybench/bench_api.py
index c93fc9dd..add3d2cd 100644
--- a/benchmarks/benchmarks/pybench/bench_api.py
+++ b/benchmarks/benchmarks/pybench/bench_api.py
@@ -1,5 +1,6 @@
+from collections.abc import Callable, Iterable
 from dataclasses import dataclass
-from typing import Any, Callable, Iterable
+from typing import Any
 
 
 @dataclass
diff --git a/benchmarks/benchmarks/pybench/director_api.py b/benchmarks/benchmarks/pybench/director_api.py
index 13af657c..e95bfe0d 100644
--- a/benchmarks/benchmarks/pybench/director_api.py
+++ b/benchmarks/benchmarks/pybench/director_api.py
@@ -7,12 +7,13 @@
 import subprocess
 import sys
 from argparse import ArgumentParser, Namespace
+from collections.abc import Iterable, Mapping, Sequence
 from copy import copy
 from dataclasses import dataclass
 from functools import cached_property
 from pathlib import Path
 from tempfile import TemporaryDirectory
-from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, TypeVar, Union
+from typing import Any, Callable, Optional, TypeVar, Union
 
 import pyperf
 from pyperf._cli import format_checks
@@ -51,7 +52,7 @@ class BenchSchema:
 @dataclass(frozen=True)
 class PlotParams:
     title: str
-    fig_size: Tuple[float, float] = (8, 4.8)
+    fig_size: tuple[float, float] = (8, 4.8)
     label_padding: float = 0
     trim_after: Optional[float] = None
     label_format: str = ".1f"
@@ -73,7 +74,7 @@ def __init__(
         self.data_dir = data_dir
         self.env_spec = env_spec
         self.all_schemas = schemas
-        self.id_to_schema: Dict[str, BenchSchema] = {self.get_id(schema): schema for schema in schemas}
+        self.id_to_schema: dict[str, BenchSchema] = {self.get_id(schema): schema for schema in schemas}
         self._base_check_params = check_params
 
     def add_arguments(self, parser: ArgumentParser) -> None:
@@ -183,7 +184,7 @@ def get_warnings(self, schema: BenchSchema) -> Optional[Sequence[str]]:
         return [*warnings, *self_warnings]
 
     def _check_yourself(self, schema: BenchSchema, bench: pyperf.Benchmark, check_params: CheckParams) -> Sequence[str]:
-        lines: List[str] = []
+        lines: list[str] = []
         stdev = bench.stdev()
         mean = bench.mean()
         rate = stdev / mean
@@ -264,7 +265,7 @@ def run_benchmarks(
             for schema in schemas
         }
 
-        benchmarks_to_run: List[str]
+        benchmarks_to_run: list[str]
         if exclude is not None:
             benchmarks_to_run = [
                 self.accessor.get_local_id(schema)
@@ -331,7 +332,7 @@ def launch_benchmark(
         self,
         bench_name: str,
         entrypoint: str,
-        params: List[Any],
+        params: list[Any],
         extra_args: Iterable[str] = (),
     ) -> None:
         subprocess.run(
@@ -452,7 +453,7 @@ def __init__(
         self.data_dir = data_dir
         self.env_spec = env_spec
         self.plot_params = plot_params
-        self.schemas: List[BenchSchema] = list(schemas)
+        self.schemas: list[BenchSchema] = list(schemas)
         self.check_params = check_params
 
     def add(self, *schemas: BenchSchema) -> None:
@@ -538,7 +539,7 @@ def make_bench_checker(self, accessor: BenchAccessor) -> BenchChecker:
         return BenchChecker(accessor)
 
     def _validate_schemas(self, accessor: BenchAccessor):
-        local_id_set: Set[str] = set()
+        local_id_set: set[str] = set()
         for schema in self.schemas:
             local_id = accessor.get_local_id(schema)
             if local_id in local_id_set:
diff --git a/benchmarks/benchmarks/pybench/parametrization.py b/benchmarks/benchmarks/pybench/parametrization.py
index b0c052e1..af7fab40 100644
--- a/benchmarks/benchmarks/pybench/parametrization.py
+++ b/benchmarks/benchmarks/pybench/parametrization.py
@@ -1,18 +1,19 @@
 import itertools
-from typing import Any, Dict, Iterable, Iterator, Mapping, Optional, TypeVar
+from collections.abc import Iterable, Iterator, Mapping
+from typing import Any, Optional, TypeVar
 
 P = TypeVar("P", bound="Parametrizer")
 
 
 class Parametrizer:
     def __init__(self, *, product: Optional[Mapping[str, Iterable[Any]]] = None) -> None:
-        self._product: Dict[str, Iterable[Any]] = {} if product is None else dict(product)
+        self._product: dict[str, Iterable[Any]] = {} if product is None else dict(product)
 
     def product(self: P, variants: Mapping[str, Iterable[Any]]) -> P:
         self._product.update(variants)
         return self
 
-    def __iter__(self) -> Iterator[Dict[str, Any]]:
+    def __iter__(self) -> Iterator[dict[str, Any]]:
         for case_values in itertools.product(*self._product.values()):
             yield dict(zip(self._product.keys(), case_values))
 
diff --git a/docs/changelog/changelog_body.rst b/docs/changelog/changelog_body.rst
index b60850bb..42f18fa7 100644
--- a/docs/changelog/changelog_body.rst
+++ b/docs/changelog/changelog_body.rst
@@ -1,6 +1,47 @@
 ----------------------------------------------------
 
 
+.. _v3.0.0b8:
+
+`3.0.0b8 <https://github.com/reagento/adaptix/tree/v3.0.0b8>`__ -- 2024-09-02
+=============================================================================
+
+.. _v3.0.0b8-Features:
+
+Features
+--------
+
+- Add new :func:`.datetime_by_timestamp` and :func:`.date_by_timestamp` provider factories. `#281 <https://github.com/reagento/adaptix/issues/281>`__
+- Add :func:`.datetime_by_format` to public API. `#286 <https://github.com/reagento/adaptix/issues/286>`__
+- Add :func:`.type_tools.exec_type_checking` function
+  to deal with cyclic references by executing ``if TYPE_CHECKING:`` constructs. `#288 <https://github.com/reagento/adaptix/issues/288>`__
+- Add support for bytes inside literal, for example ``Literal[b"abc"]``. `#318 <https://github.com/reagento/adaptix/issues/318>`__
+- The library shows a hint if one class is a model and the other is not.
+- Traceback of ``CannotProvide`` is hidden (it is raised when loader, dumper, or converter can not be created).
+  It simplifies error messages to users.
+  You can show traceback by disabling ``hide_traceback`` parameter of ``Retort``.
+
+.. _v3.0.0b8-Breaking Changes:
+
+Breaking Changes
+----------------
+
+- Drop support of Python 3.8.
+- ``TypedDictAt38Warning`` is removed.
+
+.. _v3.0.0b8-Other:
+
+Other
+-----
+
+- Refactor internal provider routing system. It becomes more simple and readable.
+  Also, internal caching is added.
+  This led to a 40% speedup in loader generation for medium models
+  and up to 4x speedup for large models with many recursive types.
+
+----------------------------------------------------
+
+
 .. _v3.0.0b7:
 
 `3.0.0b7 <https://github.com/reagento/adaptix/tree/v3.0.0b7>`__ -- 2024-06-10
diff --git a/docs/common/installation.rst b/docs/common/installation.rst
index a36cf60a..57eded5e 100644
--- a/docs/common/installation.rst
+++ b/docs/common/installation.rst
@@ -2,7 +2,7 @@ Just use pip to install the library
 
 .. code-block:: text
 
-    pip install adaptix==3.0.0b7
+    pip install adaptix==3.0.0b8
 
 
 Integrations with 3-rd party libraries are turned on automatically,
diff --git a/docs/conversion/extended-usage.rst b/docs/conversion/extended-usage.rst
index 4eafa641..d99c6b13 100644
--- a/docs/conversion/extended-usage.rst
+++ b/docs/conversion/extended-usage.rst
@@ -69,7 +69,7 @@ Using default value for fields
 ================================
 
 By default, all fields of the destination model must be linked to something
-even field is not required (has a default value).
+even if field is not required (has a default value).
 
 .. hint::
 
@@ -82,7 +82,7 @@ via :func:`.conversion.allow_unlinked_optional` and :func:`.conversion.forbid_un
 
 .. literalinclude:: /examples/conversion/extended_usage/using_default_value_for_fields.py
 
-Each parameter of these functions are predicate defining the target scope of the policy.
+Each parameter of these functions is predicate defining the target scope of the policy.
 You can use them without arguments to apply new policies to all fields.
 
 .. dropdown:: Redefine policy globally (for all fields)
diff --git a/docs/conversion/tutorial.rst b/docs/conversion/tutorial.rst
index 77462185..2c00d062 100644
--- a/docs/conversion/tutorial.rst
+++ b/docs/conversion/tutorial.rst
@@ -33,7 +33,7 @@ Also, it works for nested models.
 .. literalinclude:: /examples/conversion/tutorial/nested.py
 
 Furthermore, there is :func:`.conversion.convert` that can directly convert one model to another,
-but it is quite limited and can not configured, so it won't be considered onwards.
+but it is quite limited and can not be configured, so it won't be considered onwards.
 
 .. dropdown:: Usage of :func:`.conversion.convert`
 
diff --git a/docs/custom_ext/bench_tools.py b/docs/custom_ext/bench_tools.py
index c0996cf9..67c215c6 100644
--- a/docs/custom_ext/bench_tools.py
+++ b/docs/custom_ext/bench_tools.py
@@ -1,5 +1,4 @@
 import json
-from typing import Dict
 from zipfile import ZipFile
 
 import plotly
@@ -45,7 +44,7 @@ class CustomBenchUsedDistributions(SphinxMacroDirective):
     required_arguments = 0
 
     def generate_string(self) -> str:
-        distributions: Dict[str, str] = {}
+        distributions: dict[str, str] = {}
 
         for hub_description in BENCHMARK_HUBS:
             with ZipFile(RELEASE_DATA / f"{hub_description.key}.zip") as release_zip:
diff --git a/docs/custom_ext/macros.py b/docs/custom_ext/macros.py
index e4ae11f2..acff2619 100644
--- a/docs/custom_ext/macros.py
+++ b/docs/custom_ext/macros.py
@@ -1,8 +1,8 @@
 import tomllib
 from abc import ABC, abstractmethod
+from collections.abc import Iterable
 from pathlib import Path
 from textwrap import dedent, indent
-from typing import Iterable
 
 from docutils.statemachine import StringList
 from sphinx.util import docutils
diff --git a/docs/examples/benchmarks/gh_issues_models.py b/docs/examples/benchmarks/gh_issues_models.py
index 28441c01..7bf90dc3 100644
--- a/docs/examples/benchmarks/gh_issues_models.py
+++ b/docs/examples/benchmarks/gh_issues_models.py
@@ -1,7 +1,7 @@
 from dataclasses import dataclass
 from datetime import datetime
 from enum import Enum
-from typing import List, Optional
+from typing import Optional
 
 
 class IssueState(str, Enum):
@@ -100,9 +100,9 @@ class Issue:
     state_reason: Optional[StateReason]
     title: str
     user: Optional[SimpleUser]
-    labels: List[Label]
+    labels: list[Label]
     assignee: Optional[SimpleUser]
-    assignees: Optional[List[SimpleUser]]
+    assignees: Optional[list[SimpleUser]]
     locked: bool
     active_lock_reason: Optional[str]
     comments: int
@@ -120,4 +120,4 @@ class Issue:
 
 @dataclass
 class GetRepoIssuesResponse:
-    data: List[Issue]
+    data: list[Issue]
diff --git a/docs/examples/benchmarks/simple_structures_models.py b/docs/examples/benchmarks/simple_structures_models.py
index 7fd1659a..39d177a9 100644
--- a/docs/examples/benchmarks/simple_structures_models.py
+++ b/docs/examples/benchmarks/simple_structures_models.py
@@ -1,5 +1,4 @@
 from dataclasses import dataclass
-from typing import List
 
 
 @dataclass
@@ -14,4 +13,4 @@ class Review:
 class Book:
     id: int
     name: str
-    reviews: List[Review]  # contains 100 items
+    reviews: list[Review]  # contains 100 items
diff --git a/docs/examples/conversion/extended_usage/global_allow_unlinked_optional.py b/docs/examples/conversion/extended_usage/global_allow_unlinked_optional.py
index 99a738e3..ff92a130 100644
--- a/docs/examples/conversion/extended_usage/global_allow_unlinked_optional.py
+++ b/docs/examples/conversion/extended_usage/global_allow_unlinked_optional.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass, field
-from typing import List, Optional
+from typing import Optional
 
 from adaptix.conversion import allow_unlinked_optional, get_converter
 
@@ -17,7 +17,7 @@ class BookDTO:
     price: int
     author: str
     collection_id: Optional[int] = None
-    bookmarks_ids: List[str] = field(default_factory=list)
+    bookmarks_ids: list[str] = field(default_factory=list)
 
 
 convert_book_to_dto = get_converter(
diff --git a/docs/examples/conversion/extended_usage/link_constant.py b/docs/examples/conversion/extended_usage/link_constant.py
index 61d1a1ff..1c8b36f0 100644
--- a/docs/examples/conversion/extended_usage/link_constant.py
+++ b/docs/examples/conversion/extended_usage/link_constant.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass
-from typing import List, Optional
+from typing import Optional
 
 from adaptix import P
 from adaptix.conversion import get_converter, link_constant
@@ -18,7 +18,7 @@ class BookDTO:
     price: int
     author: str
     collection_id: Optional[int]
-    bookmarks_ids: List[str]
+    bookmarks_ids: list[str]
 
 
 convert_book_to_dto = get_converter(
diff --git a/docs/examples/loading-and-dumping/extended_usage/advanced_mapping.py b/docs/examples/loading-and-dumping/extended_usage/advanced_mapping.py
index 95144f57..8639efb5 100644
--- a/docs/examples/loading-and-dumping/extended_usage/advanced_mapping.py
+++ b/docs/examples/loading-and-dumping/extended_usage/advanced_mapping.py
@@ -1,6 +1,6 @@
 import re
+from collections.abc import Iterable, Sequence
 from dataclasses import dataclass
-from typing import Iterable, List, Sequence
 
 from adaptix import P, Retort, name_mapping
 
@@ -9,9 +9,9 @@
 class Document:
     key: str
 
-    redirects: List[str]
-    edition_keys: List[str]
-    lcc_list: List[str]
+    redirects: list[str]
+    edition_keys: list[str]
+    lcc_list: list[str]
 
 
 def create_plural_stripper(
diff --git a/docs/examples/loading-and-dumping/extended_usage/chaining.py b/docs/examples/loading-and-dumping/extended_usage/chaining.py
index fa59c4b8..ba854bbf 100644
--- a/docs/examples/loading-and-dumping/extended_usage/chaining.py
+++ b/docs/examples/loading-and-dumping/extended_usage/chaining.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass
-from typing import Any, Dict
+from typing import Any
 
 from adaptix import NameStyle, Retort, name_mapping
 
@@ -8,7 +8,7 @@
 class Person:
     first_name: str
     last_name: str
-    extra: Dict[str, Any]
+    extra: dict[str, Any]
 
 
 @dataclass
diff --git a/docs/examples/loading-and-dumping/extended_usage/chaining_overriding.py b/docs/examples/loading-and-dumping/extended_usage/chaining_overriding.py
index e675868e..214a7a8b 100644
--- a/docs/examples/loading-and-dumping/extended_usage/chaining_overriding.py
+++ b/docs/examples/loading-and-dumping/extended_usage/chaining_overriding.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass
-from typing import Any, Dict
+from typing import Any
 
 from adaptix import NameStyle, Retort, name_mapping
 
@@ -8,7 +8,7 @@
 class Person:
     first_name: str
     last_name: str
-    extra: Dict[str, Any]
+    extra: dict[str, Any]
 
 
 @dataclass
diff --git a/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/__init__.py b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/chat.py b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/chat.py
new file mode 100644
index 00000000..a4744b5a
--- /dev/null
+++ b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/chat.py
@@ -0,0 +1,13 @@
+# ruff: noqa: UP035, UP006
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, List
+
+if TYPE_CHECKING:
+    from .message import Message
+
+
+@dataclass
+class Chat:
+    id: int
+    name: str
+    messages: List["Message"]
diff --git a/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/error_on_analysis.py b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/error_on_analysis.py
new file mode 100644
index 00000000..a9594e64
--- /dev/null
+++ b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/error_on_analysis.py
@@ -0,0 +1,15 @@
+from typing import get_type_hints
+
+from .chat import Chat
+from .message import Message
+
+try:
+    get_type_hints(Chat)
+except NameError as e:
+    assert str(e) == "name 'Message' is not defined"
+
+
+try:
+    get_type_hints(Message)
+except NameError as e:
+    assert str(e) == "name 'Chat' is not defined"
diff --git a/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/main.py b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/main.py
new file mode 100644
index 00000000..ad367b8c
--- /dev/null
+++ b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/main.py
@@ -0,0 +1,22 @@
+# ruff: noqa: UP035, UP006
+from typing import List, get_type_hints
+
+from adaptix.type_tools import exec_type_checking
+
+from . import chat, message
+
+# You pass the module object
+exec_type_checking(chat)
+exec_type_checking(message)
+
+# After these types can be extracted
+assert get_type_hints(chat.Chat) == {
+    "id": int,
+    "name": str,
+    "messages": List[message.Message],
+}
+assert get_type_hints(chat.Message) == {
+    "id": int,
+    "text": str,
+    "chat": chat.Chat,
+}
diff --git a/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/message.py b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/message.py
new file mode 100644
index 00000000..03ad66ae
--- /dev/null
+++ b/docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/message.py
@@ -0,0 +1,12 @@
+from dataclasses import dataclass
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .chat import Chat
+
+
+@dataclass
+class Message:
+    id: int
+    text: str
+    chat: "Chat"
diff --git a/docs/examples/loading-and-dumping/extended_usage/fields_filtering_only.pytb b/docs/examples/loading-and-dumping/extended_usage/fields_filtering_only.pytb
index c0f73dd4..7a8f7167 100644
--- a/docs/examples/loading-and-dumping/extended_usage/fields_filtering_only.pytb
+++ b/docs/examples/loading-and-dumping/extended_usage/fields_filtering_only.pytb
@@ -1,10 +1,6 @@
-  + Exception Group Traceback (most recent call last):
-  |   ...
   | adaptix.AggregateCannotProvide: Cannot create loader for model. Cannot fetch InputNameLayout (1 sub-exception)
   | Location: `User`
   +-+---------------- 1 ----------------
-    | Traceback (most recent call last):
-    |   ...
     | adaptix.CannotProvide: Required fields ['password_hash'] are skipped
     | Location: `User`
     +------------------------------------
diff --git a/docs/examples/loading-and-dumping/extended_usage/fields_filtering_skip.pytb b/docs/examples/loading-and-dumping/extended_usage/fields_filtering_skip.pytb
index 98c2ef25..4fc4927d 100644
--- a/docs/examples/loading-and-dumping/extended_usage/fields_filtering_skip.pytb
+++ b/docs/examples/loading-and-dumping/extended_usage/fields_filtering_skip.pytb
@@ -1,10 +1,6 @@
-  + Exception Group Traceback (most recent call last):
-  |   ...
   | adaptix.AggregateCannotProvide: Cannot create loader for model. Cannot fetch InputNameLayout (1 sub-exception)
   | Location: `User`
   +-+---------------- 1 ----------------
-    | Traceback (most recent call last):
-    |   ...
     | adaptix.CannotProvide: Required fields ['password_hash'] are skipped
     | Location: `User`
     +------------------------------------
diff --git a/docs/examples/loading-and-dumping/extended_usage/omit_default.py b/docs/examples/loading-and-dumping/extended_usage/omit_default.py
index dc507fdd..850a04c0 100644
--- a/docs/examples/loading-and-dumping/extended_usage/omit_default.py
+++ b/docs/examples/loading-and-dumping/extended_usage/omit_default.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass, field
-from typing import List, Optional
+from typing import Optional
 
 from adaptix import Retort, name_mapping
 
@@ -8,7 +8,7 @@
 class Book:
     title: str
     sub_title: Optional[str] = None
-    authors: List[str] = field(default_factory=list)
+    authors: list[str] = field(default_factory=list)
 
 
 retort = Retort(
diff --git a/docs/examples/loading-and-dumping/extended_usage/omit_default_selective.py b/docs/examples/loading-and-dumping/extended_usage/omit_default_selective.py
index be9687cd..01d7fbc5 100644
--- a/docs/examples/loading-and-dumping/extended_usage/omit_default_selective.py
+++ b/docs/examples/loading-and-dumping/extended_usage/omit_default_selective.py
@@ -1,5 +1,5 @@
 from dataclasses import dataclass, field
-from typing import List, Optional
+from typing import Optional
 
 from adaptix import Retort, name_mapping
 
@@ -8,7 +8,7 @@
 class Book:
     title: str
     sub_title: Optional[str] = None
-    authors: List[str] = field(default_factory=list)
+    authors: list[str] = field(default_factory=list)
 
 
 retort = Retort(
diff --git a/docs/examples/loading-and-dumping/extended_usage/recursive_data_types.py b/docs/examples/loading-and-dumping/extended_usage/recursive_data_types.py
index 4a57f669..3233c883 100644
--- a/docs/examples/loading-and-dumping/extended_usage/recursive_data_types.py
+++ b/docs/examples/loading-and-dumping/extended_usage/recursive_data_types.py
@@ -1,3 +1,4 @@
+# ruff: noqa: UP006, UP035
 from dataclasses import dataclass
 from typing import List
 
diff --git a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extra_skip.py b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extra_skip.py
index 81420825..e2092b8f 100644
--- a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extra_skip.py
+++ b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extra_skip.py
@@ -1,5 +1,6 @@
+from collections.abc import Mapping
 from dataclasses import dataclass
-from typing import Any, Mapping
+from typing import Any
 
 from adaptix import Retort, name_mapping
 
diff --git a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extractor.py b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extractor.py
index 3ff2b9a4..a0569642 100644
--- a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extractor.py
+++ b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extractor.py
@@ -1,6 +1,7 @@
 import dataclasses
+from collections.abc import Mapping
 from dataclasses import dataclass
-from typing import Any, Mapping
+from typing import Any
 
 from adaptix import Retort, name_mapping
 
diff --git a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_field_id.py b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_field_id.py
index 2f3a0371..7c914633 100644
--- a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_field_id.py
+++ b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_field_id.py
@@ -1,5 +1,6 @@
+from collections.abc import Mapping
 from dataclasses import dataclass
-from typing import Any, Mapping
+from typing import Any
 
 from adaptix import Retort, name_mapping
 
diff --git a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_several_field_id.py b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_several_field_id.py
index 5c045edf..00f2935a 100644
--- a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_several_field_id.py
+++ b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_several_field_id.py
@@ -1,5 +1,6 @@
+from collections.abc import Mapping
 from dataclasses import dataclass
-from typing import Any, Mapping
+from typing import Any
 
 from adaptix import Retort, name_mapping
 
diff --git a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_field_id.py b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_field_id.py
index 1ea4a777..ffb78f18 100644
--- a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_field_id.py
+++ b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_field_id.py
@@ -1,5 +1,6 @@
+from collections.abc import Mapping
 from dataclasses import dataclass
-from typing import Any, Mapping
+from typing import Any
 
 from adaptix import Retort, name_mapping
 
diff --git a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_saturator.py b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_saturator.py
index 84c66273..70adb3bc 100644
--- a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_saturator.py
+++ b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_saturator.py
@@ -1,5 +1,6 @@
+from collections.abc import Mapping
 from dataclasses import dataclass
-from typing import Any, Mapping
+from typing import Any
 
 from adaptix import Retort, name_mapping
 
diff --git a/docs/examples/loading-and-dumping/tutorial/collection.py b/docs/examples/loading-and-dumping/tutorial/collection.py
index ef802488..0ec0f51d 100644
--- a/docs/examples/loading-and-dumping/tutorial/collection.py
+++ b/docs/examples/loading-and-dumping/tutorial/collection.py
@@ -1,5 +1,4 @@
 from dataclasses import dataclass
-from typing import List
 
 from adaptix import Retort
 
@@ -22,6 +21,6 @@ class Book:
 ]
 
 retort = Retort()
-books = retort.load(data, List[Book])
+books = retort.load(data, list[Book])
 assert books == [Book(title="Fahrenheit 451", price=100), Book(title="1984", price=100)]
-assert retort.dump(books, List[Book]) == data
+assert retort.dump(books, list[Book]) == data
diff --git a/docs/examples/loading-and-dumping/tutorial/predicate_system_p.py b/docs/examples/loading-and-dumping/tutorial/predicate_system_p.py
index 6a16a929..7c7e8f1b 100644
--- a/docs/examples/loading-and-dumping/tutorial/predicate_system_p.py
+++ b/docs/examples/loading-and-dumping/tutorial/predicate_system_p.py
@@ -1,6 +1,5 @@
 from dataclasses import dataclass
 from datetime import datetime, timezone
-from typing import List
 
 from adaptix import P, Retort, loader
 
@@ -21,8 +20,8 @@ class Book:
 
 @dataclass
 class Bookshop:
-    workers: List[Person]
-    books: List[Book]
+    workers: list[Person]
+    books: list[Book]
 
 
 data = {
diff --git a/docs/examples/loading-and-dumping/tutorial/retort_combination.py b/docs/examples/loading-and-dumping/tutorial/retort_combination.py
index db37e5ec..18e234fa 100644
--- a/docs/examples/loading-and-dumping/tutorial/retort_combination.py
+++ b/docs/examples/loading-and-dumping/tutorial/retort_combination.py
@@ -1,7 +1,6 @@
 from dataclasses import dataclass
 from datetime import datetime, timezone
 from enum import Enum
-from typing import List
 
 from adaptix import Retort, bound, dumper, enum_by_name, loader
 
@@ -35,7 +34,7 @@ class LiteraryWork:
 @dataclass
 class Person:
     name: str
-    works: List[LiteraryWork]
+    works: list[LiteraryWork]
 
 
 retort = Retort(
diff --git a/docs/examples/reference/integrations/sqlalchemy_json/helpers.py b/docs/examples/reference/integrations/sqlalchemy_json/helpers.py
index e49505aa..f1c7c4e1 100644
--- a/docs/examples/reference/integrations/sqlalchemy_json/helpers.py
+++ b/docs/examples/reference/integrations/sqlalchemy_json/helpers.py
@@ -1,5 +1,6 @@
+from collections.abc import Callable, Iterator
 from contextlib import contextmanager
-from typing import Any, Callable, Iterator
+from typing import Any
 
 from sqlalchemy import Engine, create_engine
 from sqlalchemy.orm import Session, sessionmaker
diff --git a/docs/index.rst b/docs/index.rst
index 51cade35..087b372e 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -33,6 +33,7 @@ Adaptix
    API <reference/api/modules>
    reference/changelog
    reference/contributing
+   reference/bugs-found-in-python
 
 .. toctree::
    :maxdepth: 2
diff --git a/docs/loading-and-dumping/extended-usage.rst b/docs/loading-and-dumping/extended-usage.rst
index 11a8b795..b7a5663a 100644
--- a/docs/loading-and-dumping/extended-usage.rst
+++ b/docs/loading-and-dumping/extended-usage.rst
@@ -39,6 +39,7 @@ Recursive data types
 These types could be loaded and dumped without additional configuration.
 
 .. literalinclude:: /examples/loading-and-dumping/extended_usage/recursive_data_types.py
+   :lines: 2-
 
 But it does not work with cyclic-referenced objects like
 
@@ -47,6 +48,42 @@ But it does not work with cyclic-referenced objects like
    item_category.sub_categories.append(item_category)
 
 
+Dealing with ``if TYPE_CHECKING``
+===================================
+
+Sometimes you want to split interdependent models into several files.
+This results in some imports being visible only to type checkers.
+Analysis of such type hints is not available at runtime.
+
+
+Let's imagine that we have two files:
+
+.. literalinclude:: /examples/loading-and-dumping/extended_usage/dealing_with_type_checking/chat.py
+   :caption: File ``chat.py``
+   :lines: 2-
+
+.. literalinclude:: /examples/loading-and-dumping/extended_usage/dealing_with_type_checking/message.py
+   :caption: File ``message.py``
+
+
+If you try to get type hints at runtime, you will fail:
+
+.. literalinclude:: /examples/loading-and-dumping/extended_usage/dealing_with_type_checking/error_on_analysis.py
+
+At runtime, these imports are not executed, so the builtin analysis function can not resolve forward refs.
+
+Adaptix can overcome this via :func:`.type_tools.exec_type_checking`.
+It extracts code fragments defined under ``if TYPE_CHECKING`` and ``if typing.TYPE_CHECKING`` constructs
+and then executes them in the context of module.
+As a result, the module namespace is filled with missing names, and *any* introspection function can acquire types.
+
+You should call ``exec_type_checking`` after all required modules can be imported.
+Usually, it must be at ``main`` module.
+
+.. literalinclude:: /examples/loading-and-dumping/extended_usage/dealing_with_type_checking/main.py
+   :caption: File ``main.py``
+   :lines: 2-
+
 Name mapping
 ========================
 
@@ -83,7 +120,7 @@ There are more complex and more powerful use cases of ``map``, which will be des
 Name style
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Sometimes JSON keys are quite normal but do fit PEP8 recommendations of variable naming.
+Sometimes JSON keys are quite normal but do not fit PEP8 recommendations of variable naming.
 You can rename each field individually, but library can automatically translate such names.
 
 .. literalinclude:: /examples/loading-and-dumping/extended_usage/name_style.py
@@ -179,7 +216,7 @@ Parameter :paramref:`.name_mapping.extra_in` controls policy how extra data is s
 :obj:`.ExtraSkip`
 """""""""""""""""""""""
 
-Default behaviour. All extra data is ignored.
+Default behavior. All extra data is ignored.
 
 .. literalinclude:: /examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_extra_skip.py
 
@@ -251,7 +288,7 @@ Parameter :paramref:`.name_mapping.extra_in` controls policy how extra data is e
 :obj:`.ExtraSkip`
 """""""""""""""""""""""
 
-Default behaviour. All extra data is ignored.
+Default behavior. All extra data is ignored.
 
 .. literalinclude:: /examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_dumping_extra_skip.py
 
diff --git a/docs/loading-and-dumping/specific-types-behavior.rst b/docs/loading-and-dumping/specific-types-behavior.rst
index cba7e3e7..8c544a3b 100644
--- a/docs/loading-and-dumping/specific-types-behavior.rst
+++ b/docs/loading-and-dumping/specific-types-behavior.rst
@@ -109,7 +109,13 @@ dumper serialize value via ``__str__`` method.
 date, time and datetime
 '''''''''''''''''''''''''''
 
-Value is represented as an isoformat string.
+By default value is represented as an isoformat string.
+
+But you can override this behaviour with other providers:
+
+- To load and dump ``datetime`` to / from specific format, you can use :func:`.datetime_by_format`
+- To load and dump ``datetime`` to / from UNIX timestamp, you can use :func:`.datetime_by_timestamp`
+- To load and dump ``date`` from UNIX timestamp, you can use :func:`.date_by_timestamp`
 
 timedelta
 '''''''''''''''''''''''''''
@@ -137,7 +143,7 @@ Enum members are represented by their value without any conversion.
 LiteralString
 '''''''''''''''''''''''
 
-Loader and dumper have same behaviour as builtin one's of ``str`` type
+Loader and dumper have same behavior as builtin one's of ``str`` type
 
 Compound types
 ================
@@ -164,12 +170,13 @@ Literal
 Loader accepts only values listed in ``Literal``.
 If ``strict_coercion`` is enabled, the loader will distinguish equal ``bool`` and ``int`` instances,
 otherwise, they will be considered as same values.
-``Enum`` instances will be loaded via its loaders. Enum loaders have a higher priority over others, that is, they will be applied first.
+``Enum`` instances will be loaded via its loaders. ``bytes`` instances (e.g ``b"abc"``) will be loaded via its loaders as well.
+Enum loaders have a higher priority over others, that is, they will be applied first.
 
 If the input value could be interpreted as several ``Literal`` members, the result will be undefined.
 
 Dumper will return value without any processing excluding ``Enum`` instances,
-they will be processed via the corresponding dumper.
+they will be processed via the corresponding dumper. ``bytes`` instances also will be processed via the corresponding dumper.
 
 Be careful when you use a ``0``, ``1``, ``False`` and ``True`` as ``Literal`` members.
 Due to type hint caching ``Literal[0, 1]`` sometimes returns ``Literal[False, True]``.
@@ -248,4 +255,4 @@ Also, the model could be loaded from the list.
 
 Dumper works similarly and produces dict (or list).
 
-See :ref:`supported-model-kinds` for exact list of supported model.
+See :ref:`supported-model-kinds` for exact list of supported models.
diff --git a/docs/overview.rst b/docs/overview.rst
index 2e1af717..728cb09e 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -16,7 +16,7 @@ Installation
 
 .. code-block:: text
 
-    pip install adaptix==3.0.0b7
+    pip install adaptix==3.0.0b8
 
 
 Example
@@ -33,7 +33,7 @@ Example
 Requirements
 ==================
 
-* Python 3.8+
+* Python 3.9+
 
 
 Use cases
diff --git a/docs/reference/bugs-found-in-python.rst b/docs/reference/bugs-found-in-python.rst
new file mode 100644
index 00000000..1648605e
--- /dev/null
+++ b/docs/reference/bugs-found-in-python.rst
@@ -0,0 +1,32 @@
+========================
+Bugs found in Python
+========================
+
+Adaptix is a sophisticated project with scrupulous approach to testing.
+This leads to the situation where sometimes bugs are found in the Python interpreter itself.
+
+Type alias cannot be created from type alias
+=====================================================
+
+The first release of Python 3.11 contains bug preventing parametrizing type aliases with ``TypeVar``
+due to support of ``TypeVarTuple``.
+Therefore adaptix couldn't even be imported. The next Python patch fixes this.
+
+:octicon:`mark-github` `Issue #98852 <https://github.com/python/cpython/issues/98852>`__
+
+
+``date.fromtimestamp(None)`` returns current date
+======================================================
+
+CPython has two implementations of ``datetime`` module:
+the pure python ``_pydatetime`` and optimized ``_datetime`` written in ``C``.
+
+The ``_pydatetime.date.fromtimestamp`` accepts ``None`` instead of ``int`` and returns current date.
+This was occurring due to the usage of the ``time.localtime(t)`` function.
+
+By default, the ``C``-version of module is used, but you can disable it via ``Modules/Setup.local`` file.
+Also PyPy uses pure-python version of datetime module that reveals this bug.
+
+:func:`.date_by_timestamp` works the same on any python version.
+
+:octicon:`mark-github` `Issue #120268 <https://github.com/python/cpython/issues/120268>`__
diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst
index 57dabacc..692502f5 100644
--- a/docs/reference/changelog.rst
+++ b/docs/reference/changelog.rst
@@ -12,7 +12,7 @@ After that, breaking changes will only be introduced in major versions.
 
 .. custom-non-guaranteed-behavior::
 
-  Some aspects is behavior are not guaranteed and could be changed at any release without any mention in the changelog
+  Some aspects of behavior are not guaranteed and could be changed at any release without any mention in the changelog
   (or even vary in different environments or different runs).
 
   Such details are highlighted in the documentation via this admonition.
diff --git a/docs/reference/contributing.rst b/docs/reference/contributing.rst
index a95bcf1e..7edddb40 100644
--- a/docs/reference/contributing.rst
+++ b/docs/reference/contributing.rst
@@ -16,7 +16,6 @@ How to setup the repository
 
 #. Install all needed python interpreters
 
-   * CPython 3.8
    * CPython 3.9
    * CPython 3.10
    * CPython 3.11
diff --git a/examples/api_division/models.py b/examples/api_division/models.py
index 85232ad1..a82f7c7b 100644
--- a/examples/api_division/models.py
+++ b/examples/api_division/models.py
@@ -1,7 +1,7 @@
 from dataclasses import dataclass
 from decimal import Decimal
 from enum import Enum, IntEnum
-from typing import List, Literal, Optional, Union
+from typing import Literal, Optional, Union
 
 from phonenumbers import PhoneNumber
 
@@ -46,7 +46,7 @@ class RecItem:
 @dataclass(frozen=True)
 class Receipt:
     type: ReceiptType
-    items: List[RecItem]
+    items: list[RecItem]
     taxation: Taxation
-    notify: Optional[List[NotifyTarget]]
+    notify: Optional[list[NotifyTarget]]
     version: Literal["1"] = "1"
diff --git a/examples/api_division/retort.py b/examples/api_division/retort.py
index bbec6484..13a352bc 100644
--- a/examples/api_division/retort.py
+++ b/examples/api_division/retort.py
@@ -1,5 +1,4 @@
 from decimal import Decimal
-from typing import List
 
 import phonenumbers
 from phonenumbers import PhoneNumber
@@ -81,7 +80,7 @@ def forbid_version_key(data):
 
 outer_receipt_retort = _base_retort.extend(
     recipe=[
-        validator(List[RecItem], lambda x: len(x) > 0, "At least one item must be presented"),
+        validator(list[RecItem], lambda x: len(x) > 0, "At least one item must be presented"),
         validator(P[RecItem].quantity, lambda x: x > Decimal(0), "Value must be > 0"),
         validator(P[RecItem].price, lambda x: x >= Money(0), "Value must be >= 0"),
 
diff --git a/examples/simple_api_processing/models.py b/examples/simple_api_processing/models.py
index db7cc977..e7d632d5 100644
--- a/examples/simple_api_processing/models.py
+++ b/examples/simple_api_processing/models.py
@@ -1,6 +1,6 @@
 from dataclasses import dataclass, field
 from datetime import datetime
-from typing import List, Optional
+from typing import Optional
 
 
 @dataclass
@@ -14,7 +14,7 @@ class Weather:
 @dataclass
 class Forecast:
     timestamp: datetime
-    weather: List[Weather]
+    weather: list[Weather]
 
     clouds: int
     dew_point: float
@@ -40,7 +40,7 @@ class ForecastPack:
     timezone_offset: int
 
     current: Optional[Forecast] = None
-    minutely: List[Forecast] = field(default_factory=list)
-    hourly: List[Forecast] = field(default_factory=list)
-    daily: List[Forecast] = field(default_factory=list)
-    alerts: List[Alert] = field(default_factory=list)
+    minutely: list[Forecast] = field(default_factory=list)
+    hourly: list[Forecast] = field(default_factory=list)
+    daily: list[Forecast] = field(default_factory=list)
+    alerts: list[Alert] = field(default_factory=list)
diff --git a/justfile b/justfile
index 455b018c..d6172875 100644
--- a/justfile
+++ b/justfile
@@ -7,15 +7,15 @@ set windows-powershell := true
 # prepare venv and repo for developing
 @bootstrap:
     pip install -r requirements/pre.txt
-    pip install -e .
-    pip install -r requirements/dev.txt
+    uv pip install -e .
+    uv pip install -r requirements/dev.txt
     pre-commit
     pre-commit install
 
 # sync version of installed packages
 @venv-sync:
-    pip-sync requirements/pre.txt requirements/dev.txt
-    pip install -e .
+    uv pip sync requirements/pre.txt requirements/dev.txt
+    uv pip install -e .
 
 # run all linters
 @lint:
@@ -68,7 +68,7 @@ doc_target := "docs-build"
 [private]
 @setup-runner:
     pip install -r requirements/pre.txt
-    pip install -r requirements/runner.txt
+    uv pip install -r requirements/runner.txt
 
 [private]
 @inv *ARGS:
diff --git a/pyproject.toml b/pyproject.toml
index dc21b88a..4f4091ec 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,16 +1,15 @@
 [build-system]
-requires = ['setuptools==69.1.0']
+requires = ['setuptools==74.1.0']
 build-backend = 'setuptools.build_meta'
 
 [project]
 name = 'adaptix'
-version = '3.0.0b7'
+version = '3.0.0b8'
 description = 'An extremely flexible and configurable data model conversion library'
 readme = 'README.md'
-requires-python = '>=3.8'
+requires-python = '>=3.9'
 dependencies = [
     'exceptiongroup>=1.1.3; python_version<"3.11"',
-    'astunparse>=1.6.3; python_version<="3.8"',
 ]
 
 classifiers = [
@@ -35,11 +34,11 @@ classifiers = [
 
 [project.optional-dependencies]
 attrs = ['attrs >= 21.3.0']
-attrs-strict = ['attrs >= 21.3.0, <= 23.2.0']
+attrs-strict = ['attrs >= 21.3.0, <= 24.2.0']
 sqlalchemy = ['sqlalchemy >= 2.0.0']
-sqlalchemy-strict = ['sqlalchemy >= 2.0.0, <= 2.0.30']
+sqlalchemy-strict = ['sqlalchemy >= 2.0.0, <= 2.0.32']
 pydantic = ['pydantic >= 2.0.0']
-pydantic-strict = ['pydantic >= 2.0.0, <= 2.7.1']
+pydantic-strict = ['pydantic >= 2.0.0, <= 2.8.2']
 
 [project.urls]
 'Homepage' = 'https://github.com/reagento/adaptix'
@@ -73,13 +72,17 @@ python_files = [
   'local_helpers.py',
 ]
 testpaths = ['tests', 'examples']
-filterwarnings = ['ignore::adaptix.TypedDictAt38Warning']
 
 [tool.coverage.run]
 branch = true
 relative_files = true
 include = ["src/**"]
 
+[tool.coverage.report]
+exclude_also = [
+    "^\\s*\\.\\.\\.\\s*(:?#.*)?$",
+]
+
 #   ┌             ┐
 #   │   LINTING   │
 #   └             ┘
@@ -114,6 +117,7 @@ ignore_decorators = ['@_aspect_storage.add', '@overload', '@abstractmethod']
 
 [tool.ruff]
 line-length = 120
+output-format = "concise"
 
 [tool.ruff.lint]
 select = ['ALL']
@@ -159,6 +163,8 @@ ignore = [
   'TD002',
   'PTH201',
   'RSE102',
+  'TCH003',
+  'PYI025',
 
   # Rules conflictin with other tools
   'I001',
@@ -167,7 +173,7 @@ ignore = [
 [tool.ruff.lint.per-file-ignores]
 "__init__.py" = ['F401']
 
-"test_*" = ['S101', 'PLR2004', 'PLC0105', 'N806', 'FA102']
+"test_*" = ['S101', 'PLR2004', 'PLC0105', 'N806', 'FA102', 'UP035', 'UP006']
 "tests/*/local_helpers.py" = ['S101', 'PLR2004', 'PLC0105', 'N806', 'FA102']
 "tests/*/data_*.py" = ['F821']
 "tests/tests_helpers/*" = ['INP001', 'S101']
diff --git a/requirements/bench.txt b/requirements/bench.txt
index c15e8efd..0ce6a7a1 100644
--- a/requirements/bench.txt
+++ b/requirements/bench.txt
@@ -1,14 +1,10 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/bench.txt --strip-extras requirements/raw/bench.txt
-#
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/bench.txt -o requirements/bench.txt --allow-unsafe --strip-extras --no-strip-markers
 -e ./benchmarks
     # via -r requirements/raw/bench.txt
-annotated-types==0.6.0
+annotated-types==0.7.0
     # via pydantic
-attrs==23.2.0
+attrs==24.2.0
     # via cattrs
 cattrs==23.1.2
     # via -r requirements/raw/bench.txt
@@ -20,9 +16,9 @@ marshmallow==3.20.1
     # via -r requirements/raw/bench.txt
 mashumaro==3.10
     # via -r requirements/raw/bench.txt
-msgspec==0.18.4 ; implementation_name != "pypy"
+msgspec==0.18.4 ; implementation_name != 'pypy'
     # via -r requirements/raw/bench.txt
-packaging==24.0
+packaging==24.1
     # via
     #   marshmallow
     #   pytest
@@ -32,9 +28,9 @@ psutil==5.9.5
     # via
     #   -r requirements/raw/bench.txt
     #   pyperf
-pydantic==2.7.1
+pydantic==2.8.2
     # via -r requirements/raw/bench.txt
-pydantic-core==2.18.2
+pydantic-core==2.20.1
     # via pydantic
 pyperf==2.6.1
     # via -r requirements/raw/bench.txt
@@ -42,7 +38,7 @@ pytest==7.4.2
     # via -r requirements/raw/bench.txt
 schematics==2.1.1
     # via -r requirements/raw/bench.txt
-typing-extensions==4.11.0
+typing-extensions==4.12.2
     # via
     #   mashumaro
     #   pydantic
diff --git a/requirements/bench_pypy38.txt b/requirements/bench_pypy38.txt
deleted file mode 100644
index adcd49cf..00000000
--- a/requirements/bench_pypy38.txt
+++ /dev/null
@@ -1,49 +0,0 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/bench_pypy38.txt --strip-extras requirements/raw/bench_pypy38.txt
-#
--e ./benchmarks
-    # via -r requirements/raw/bench_pypy38.txt
-annotated-types==0.6.0
-    # via pydantic
-attrs==23.2.0
-    # via cattrs
-cattrs==23.1.2
-    # via -r requirements/raw/bench_pypy38.txt
-dataclass-factory==2.16
-    # via -r requirements/raw/bench_pypy38.txt
-iniconfig==2.0.0
-    # via pytest
-marshmallow==3.20.1
-    # via -r requirements/raw/bench_pypy38.txt
-mashumaro==3.10
-    # via -r requirements/raw/bench_pypy38.txt
-msgspec==0.18.4 ; implementation_name != "pypy"
-    # via -r requirements/raw/bench_pypy38.txt
-packaging==24.0
-    # via
-    #   marshmallow
-    #   pytest
-pluggy==1.5.0
-    # via pytest
-psutil==5.9.5
-    # via
-    #   -r requirements/raw/bench_pypy38.txt
-    #   pyperf
-pydantic==2.5.3
-    # via -r requirements/raw/bench_pypy38.txt
-pydantic-core==2.14.6
-    # via pydantic
-pyperf==2.6.1
-    # via -r requirements/raw/bench_pypy38.txt
-pytest==7.4.2
-    # via -r requirements/raw/bench_pypy38.txt
-schematics==2.1.1
-    # via -r requirements/raw/bench_pypy38.txt
-typing-extensions==4.11.0
-    # via
-    #   mashumaro
-    #   pydantic
-    #   pydantic-core
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 3fa8f344..8ce84bd3 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -1,34 +1,28 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/dev.txt --strip-extras requirements/raw/dev.txt
-#
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/dev.txt -o requirements/dev.txt --allow-unsafe --strip-extras --no-strip-markers
 -e ./benchmarks
     # via -r requirements/raw/bench.txt
 -e ./tests/tests_helpers
     # via -r requirements/raw/test_extra_none.txt
-alabaster==0.7.16
+alabaster==1.0.0
     # via sphinx
-annotated-types==0.6.0
+annotated-types==0.7.0
     # via pydantic
 astpath==0.9.1
     # via -r requirements/raw/lint.txt
-attrs==23.2.0
+attrs==24.2.0
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   cattrs
-babel==2.14.0
+babel==2.16.0
     # via sphinx
 beautifulsoup4==4.12.3
     # via furo
-build==1.2.1
-    # via pip-tools
-cachetools==5.3.3
+cachetools==5.5.0
     # via tox
 cattrs==23.1.2
     # via -r requirements/raw/bench.txt
-certifi==2024.2.2
+certifi==2024.8.30
     # via requests
 cfgv==3.4.0
     # via pre-commit
@@ -37,14 +31,12 @@ chardet==5.2.0
 charset-normalizer==3.3.2
     # via requests
 click==8.1.7
-    # via
-    #   pip-tools
-    #   towncrier
+    # via towncrier
 colorama==0.4.6
     # via
     #   radon
     #   tox
-contourpy==1.2.1
+contourpy==1.3.0
     # via matplotlib
 coverage==7.4.4
     # via
@@ -60,7 +52,7 @@ distlib==0.3.8
     # via virtualenv
 dlint==0.14.1
     # via -r requirements/raw/lint.txt
-docutils==0.20.1
+docutils==0.21.2
     # via
     #   docutils-stubs
     #   myst-parser
@@ -69,42 +61,42 @@ docutils==0.20.1
     #   sphinx-paramlinks
 docutils-stubs==0.0.22
     # via -r requirements/raw/doc.txt
-filelock==3.13.4
+filelock==3.15.4
     # via
     #   tox
     #   virtualenv
-flake8==7.0.0
+flake8==7.1.1
     # via dlint
-fonttools==4.51.0
+fonttools==4.53.1
     # via matplotlib
-furo==2024.1.29
+furo==2024.8.6
     # via -r requirements/raw/doc.txt
 gitdb==4.0.11
     # via gitpython
 gitpython==3.1.43
     # via -r requirements/raw/doc.txt
-greenlet==3.0.3
+greenlet==3.0.3 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')
     # via sqlalchemy
-identify==2.5.36
+identify==2.6.0
     # via pre-commit
-idna==3.7
+idna==3.8
     # via requests
 imagesize==1.4.1
     # via sphinx
-incremental==22.10.0
+incremental==24.7.2
     # via towncrier
 iniconfig==2.0.0
     # via pytest
 invoke==2.2.0
     # via -r requirements/raw/runner.txt
-jinja2==3.1.3
+jinja2==3.1.4
     # via
     #   myst-parser
     #   sphinx
     #   towncrier
 kiwisolver==1.4.5
     # via matplotlib
-lxml==5.2.1
+lxml==5.3.0
     # via astpath
 mando==0.7.1
     # via radon
@@ -122,27 +114,26 @@ matplotlib==3.8.2
     # via -r requirements/raw/_bench-orchestrate.txt
 mccabe==0.7.0
     # via flake8
-mdit-py-plugins==0.4.0
+mdit-py-plugins==0.4.1
     # via myst-parser
 mdurl==0.1.2
     # via markdown-it-py
-msgspec==0.18.4 ; implementation_name != "pypy"
+msgspec==0.18.4 ; implementation_name != 'pypy'
     # via -r requirements/raw/bench.txt
-mypy==1.9.0
+mypy==1.11.1
     # via -r requirements/raw/lint.txt
 mypy-extensions==1.0.0
     # via mypy
-myst-parser==2.0.0
+myst-parser==4.0.0
     # via -r requirements/raw/doc.txt
-nodeenv==1.8.0
+nodeenv==1.9.1
     # via pre-commit
 numpy==1.26.4
     # via
     #   contourpy
     #   matplotlib
-packaging==24.0
+packaging==24.1
     # via
-    #   build
     #   marshmallow
     #   matplotlib
     #   plotly
@@ -150,19 +141,20 @@ packaging==24.0
     #   pytest
     #   sphinx
     #   tox
-pbr==6.0.0
+    #   tox-uv
+pbr==6.1.0
     # via sphinxcontrib-apidoc
 phonenumberslite==8.13.26
     # via -r requirements/raw/test_extra_none.txt
-pillow==10.3.0
+pillow==10.4.0
     # via matplotlib
-pip-tools==7.4.1
-    # via -r requirements/raw/dev.txt
-platformdirs==4.2.0
+pip==24.2
+    # via -r requirements/raw/pre.txt
+platformdirs==4.2.2
     # via
     #   tox
     #   virtualenv
-plotly==5.21.0
+plotly==5.23.0
     # via
     #   -r requirements/raw/_bench-orchestrate.txt
     #   -r requirements/raw/doc.txt
@@ -170,36 +162,32 @@ pluggy==1.5.0
     # via
     #   pytest
     #   tox
-pre-commit==3.7.0
+pre-commit==3.8.0
     # via -r requirements/raw/lint.txt
 psutil==5.9.5
     # via
     #   -r requirements/raw/bench.txt
     #   pyperf
-pycodestyle==2.11.1
+pycodestyle==2.12.1
     # via flake8
-pydantic==2.7.1
+pydantic==2.8.2
     # via
     #   -r requirements/raw/bench.txt
     #   -r requirements/raw/test_extra_new.txt
-pydantic-core==2.18.2
+pydantic-core==2.20.1
     # via pydantic
 pyflakes==3.2.0
     # via flake8
-pygments==2.17.2
+pygments==2.18.0
     # via
     #   furo
     #   sphinx
-pyparsing==3.1.2
+pyparsing==3.1.4
     # via matplotlib
 pyperf==2.6.1
     # via -r requirements/raw/bench.txt
-pyproject-api==1.6.1
+pyproject-api==1.7.1
     # via tox
-pyproject-hooks==1.0.0
-    # via
-    #   build
-    #   pip-tools
 pytest==7.4.2
     # via
     #   -r requirements/raw/bench.txt
@@ -208,18 +196,22 @@ python-dateutil==2.9.0.post0
     # via matplotlib
 pytz==2024.1
     # via dirty-equals
-pyyaml==6.0.1
+pyyaml==6.0.2
     # via
     #   myst-parser
     #   pre-commit
 radon==6.0.1
     # via -r requirements/raw/lint.txt
-requests==2.31.0
+requests==2.32.3
     # via sphinx
-ruff==0.4.1
+ruff==0.6.2
     # via -r requirements/raw/lint.txt
 schematics==2.1.1
     # via -r requirements/raw/bench.txt
+setuptools==69.5.1
+    # via
+    #   -r requirements/raw/dev.txt
+    #   incremental
 six==1.16.0
     # via
     #   mando
@@ -228,9 +220,9 @@ smmap==5.0.1
     # via gitdb
 snowballstemmer==2.2.0
     # via sphinx
-soupsieve==2.5
+soupsieve==2.6
     # via beautifulsoup4
-sphinx==7.3.7
+sphinx==8.0.2
     # via
     #   -r requirements/raw/doc.txt
     #   furo
@@ -248,65 +240,58 @@ sphinx-better-subsection==0.2
     # via -r requirements/raw/doc.txt
 sphinx-copybutton==0.5.2
     # via -r requirements/raw/doc.txt
-sphinx-design==0.5.0
+sphinx-design==0.6.1
     # via -r requirements/raw/doc.txt
 sphinx-paramlinks==0.6.0
     # via -r requirements/raw/doc.txt
-sphinx-reredirects==0.1.3
+sphinx-reredirects==0.1.5
     # via -r requirements/raw/doc.txt
-sphinxcontrib-apidoc==0.4.0
+sphinxcontrib-apidoc==0.5.0
     # via -r requirements/raw/doc.txt
-sphinxcontrib-applehelp==1.0.8
+sphinxcontrib-applehelp==2.0.0
     # via sphinx
-sphinxcontrib-devhelp==1.0.6
+sphinxcontrib-devhelp==2.0.0
     # via sphinx
-sphinxcontrib-htmlhelp==2.0.5
+sphinxcontrib-htmlhelp==2.1.0
     # via sphinx
 sphinxcontrib-jsmath==1.0.1
     # via sphinx
-sphinxcontrib-qthelp==1.0.7
+sphinxcontrib-qthelp==2.0.0
     # via sphinx
-sphinxcontrib-serializinghtml==1.1.10
+sphinxcontrib-serializinghtml==2.0.0
     # via sphinx
 sphinxext-opengraph==0.9.1
     # via -r requirements/raw/doc.txt
-sqlalchemy==2.0.30
+sqlalchemy==2.0.32
     # via -r requirements/raw/test_extra_new.txt
-tenacity==8.2.3
+tenacity==9.0.0
     # via plotly
 toml==0.10.2
     # via vulture
 towncrier==23.11.0
     # via -r requirements/raw/dev.txt
-tox==4.14.2
+tox==4.18.0
+    # via tox-uv
+tox-uv==1.11.2
     # via -r requirements/raw/runner.txt
-typing-extensions==4.11.0
+typing-extensions==4.12.2
     # via
     #   mashumaro
     #   mypy
     #   pydantic
     #   pydantic-core
     #   sqlalchemy
-urllib3==2.2.1
+urllib3==2.2.2
     # via requests
-virtualenv==20.25.3
+uv==0.3.2
+    # via
+    #   -r requirements/raw/pre.txt
+    #   tox-uv
+virtualenv==20.26.3
     # via
     #   pre-commit
     #   tox
 vulture==2.10
     # via -r requirements/raw/lint.txt
-wheel==0.43.0
-    # via
-    #   -r requirements/raw/pre.txt
-    #   pip-tools
-
-# The following packages are considered to be unsafe in a requirements file:
-pip==24.0
-    # via
-    #   -r requirements/raw/pre.txt
-    #   pip-tools
-setuptools==69.5.1
-    # via
-    #   -r requirements/raw/dev.txt
-    #   nodeenv
-    #   pip-tools
+wheel==0.44.0
+    # via -r requirements/raw/pre.txt
diff --git a/requirements/doc.txt b/requirements/doc.txt
index b9276e93..0e7d5cb2 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -1,30 +1,26 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/doc.txt --strip-extras requirements/raw/doc.txt
-#
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/doc.txt -o requirements/doc.txt --allow-unsafe --strip-extras --no-strip-markers
 -e ./benchmarks
     # via -r requirements/raw/bench.txt
-alabaster==0.7.16
+alabaster==1.0.0
     # via sphinx
-annotated-types==0.6.0
+annotated-types==0.7.0
     # via pydantic
-attrs==23.2.0
+attrs==24.2.0
     # via cattrs
-babel==2.14.0
+babel==2.16.0
     # via sphinx
 beautifulsoup4==4.12.3
     # via furo
 cattrs==23.1.2
     # via -r requirements/raw/bench.txt
-certifi==2024.2.2
+certifi==2024.8.30
     # via requests
 charset-normalizer==3.3.2
     # via requests
 dataclass-factory==2.16
     # via -r requirements/raw/bench.txt
-docutils==0.20.1
+docutils==0.21.2
     # via
     #   docutils-stubs
     #   myst-parser
@@ -33,19 +29,19 @@ docutils==0.20.1
     #   sphinx-paramlinks
 docutils-stubs==0.0.22
     # via -r requirements/raw/doc.txt
-furo==2024.1.29
+furo==2024.8.6
     # via -r requirements/raw/doc.txt
 gitdb==4.0.11
     # via gitpython
 gitpython==3.1.43
     # via -r requirements/raw/doc.txt
-idna==3.7
+idna==3.8
     # via requests
 imagesize==1.4.1
     # via sphinx
 iniconfig==2.0.0
     # via pytest
-jinja2==3.1.3
+jinja2==3.1.4
     # via
     #   myst-parser
     #   sphinx
@@ -59,23 +55,23 @@ marshmallow==3.20.1
     # via -r requirements/raw/bench.txt
 mashumaro==3.10
     # via -r requirements/raw/bench.txt
-mdit-py-plugins==0.4.0
+mdit-py-plugins==0.4.1
     # via myst-parser
 mdurl==0.1.2
     # via markdown-it-py
-msgspec==0.18.4 ; implementation_name != "pypy"
+msgspec==0.18.4 ; implementation_name != 'pypy'
     # via -r requirements/raw/bench.txt
-myst-parser==2.0.0
+myst-parser==4.0.0
     # via -r requirements/raw/doc.txt
-packaging==24.0
+packaging==24.1
     # via
     #   marshmallow
     #   plotly
     #   pytest
     #   sphinx
-pbr==6.0.0
+pbr==6.1.0
     # via sphinxcontrib-apidoc
-plotly==5.21.0
+plotly==5.23.0
     # via -r requirements/raw/doc.txt
 pluggy==1.5.0
     # via pytest
@@ -83,11 +79,11 @@ psutil==5.9.5
     # via
     #   -r requirements/raw/bench.txt
     #   pyperf
-pydantic==2.7.1
+pydantic==2.8.2
     # via -r requirements/raw/bench.txt
-pydantic-core==2.18.2
+pydantic-core==2.20.1
     # via pydantic
-pygments==2.17.2
+pygments==2.18.0
     # via
     #   furo
     #   sphinx
@@ -95,9 +91,9 @@ pyperf==2.6.1
     # via -r requirements/raw/bench.txt
 pytest==7.4.2
     # via -r requirements/raw/bench.txt
-pyyaml==6.0.1
+pyyaml==6.0.2
     # via myst-parser
-requests==2.31.0
+requests==2.32.3
     # via sphinx
 schematics==2.1.1
     # via -r requirements/raw/bench.txt
@@ -105,9 +101,9 @@ smmap==5.0.1
     # via gitdb
 snowballstemmer==2.2.0
     # via sphinx
-soupsieve==2.5
+soupsieve==2.6
     # via beautifulsoup4
-sphinx==7.3.7
+sphinx==8.0.2
     # via
     #   -r requirements/raw/doc.txt
     #   furo
@@ -125,34 +121,34 @@ sphinx-better-subsection==0.2
     # via -r requirements/raw/doc.txt
 sphinx-copybutton==0.5.2
     # via -r requirements/raw/doc.txt
-sphinx-design==0.5.0
+sphinx-design==0.6.1
     # via -r requirements/raw/doc.txt
 sphinx-paramlinks==0.6.0
     # via -r requirements/raw/doc.txt
-sphinx-reredirects==0.1.3
+sphinx-reredirects==0.1.5
     # via -r requirements/raw/doc.txt
-sphinxcontrib-apidoc==0.4.0
+sphinxcontrib-apidoc==0.5.0
     # via -r requirements/raw/doc.txt
-sphinxcontrib-applehelp==1.0.8
+sphinxcontrib-applehelp==2.0.0
     # via sphinx
-sphinxcontrib-devhelp==1.0.6
+sphinxcontrib-devhelp==2.0.0
     # via sphinx
-sphinxcontrib-htmlhelp==2.0.5
+sphinxcontrib-htmlhelp==2.1.0
     # via sphinx
 sphinxcontrib-jsmath==1.0.1
     # via sphinx
-sphinxcontrib-qthelp==1.0.7
+sphinxcontrib-qthelp==2.0.0
     # via sphinx
-sphinxcontrib-serializinghtml==1.1.10
+sphinxcontrib-serializinghtml==2.0.0
     # via sphinx
 sphinxext-opengraph==0.9.1
     # via -r requirements/raw/doc.txt
-tenacity==8.2.3
+tenacity==9.0.0
     # via plotly
-typing-extensions==4.11.0
+typing-extensions==4.12.2
     # via
     #   mashumaro
     #   pydantic
     #   pydantic-core
-urllib3==2.2.1
+urllib3==2.2.2
     # via requests
diff --git a/requirements/lint.txt b/requirements/lint.txt
index c3064b48..8bffdee6 100644
--- a/requirements/lint.txt
+++ b/requirements/lint.txt
@@ -1,38 +1,34 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/lint.txt --strip-extras requirements/raw/lint.txt
-#
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/lint.txt -o requirements/lint.txt --allow-unsafe --strip-extras --no-strip-markers
 -e ./benchmarks
     # via -r requirements/raw/bench.txt
 -e ./tests/tests_helpers
     # via -r requirements/raw/test_extra_none.txt
-alabaster==0.7.16
+alabaster==1.0.0
     # via sphinx
-annotated-types==0.6.0
+annotated-types==0.7.0
     # via pydantic
 astpath==0.9.1
     # via -r requirements/raw/lint.txt
-attrs==23.2.0
+attrs==24.2.0
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   cattrs
-babel==2.14.0
+babel==2.16.0
     # via sphinx
 beautifulsoup4==4.12.3
     # via furo
 cattrs==23.1.2
     # via -r requirements/raw/bench.txt
-certifi==2024.2.2
+certifi==2024.8.30
     # via requests
 cfgv==3.4.0
     # via pre-commit
 charset-normalizer==3.3.2
     # via requests
-colorama==0.4.6
+colorama==0.4.6 ; python_full_version >= '3.5'
     # via radon
-contourpy==1.2.1
+contourpy==1.3.0
     # via matplotlib
 coverage==7.4.4
     # via -r requirements/raw/test_extra_none.txt
@@ -46,7 +42,7 @@ distlib==0.3.8
     # via virtualenv
 dlint==0.14.1
     # via -r requirements/raw/lint.txt
-docutils==0.20.1
+docutils==0.21.2
     # via
     #   docutils-stubs
     #   myst-parser
@@ -55,35 +51,35 @@ docutils==0.20.1
     #   sphinx-paramlinks
 docutils-stubs==0.0.22
     # via -r requirements/raw/doc.txt
-filelock==3.13.4
+filelock==3.15.4
     # via virtualenv
-flake8==7.0.0
+flake8==7.1.1
     # via dlint
-fonttools==4.51.0
+fonttools==4.53.1
     # via matplotlib
-furo==2024.1.29
+furo==2024.8.6
     # via -r requirements/raw/doc.txt
 gitdb==4.0.11
     # via gitpython
 gitpython==3.1.43
     # via -r requirements/raw/doc.txt
-greenlet==3.0.3
+greenlet==3.0.3 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')
     # via sqlalchemy
-identify==2.5.36
+identify==2.6.0
     # via pre-commit
-idna==3.7
+idna==3.8
     # via requests
 imagesize==1.4.1
     # via sphinx
 iniconfig==2.0.0
     # via pytest
-jinja2==3.1.3
+jinja2==3.1.4
     # via
     #   myst-parser
     #   sphinx
 kiwisolver==1.4.5
     # via matplotlib
-lxml==5.2.1
+lxml==5.3.0
     # via astpath
 mando==0.7.1
     # via radon
@@ -101,66 +97,66 @@ matplotlib==3.8.2
     # via -r requirements/raw/_bench-orchestrate.txt
 mccabe==0.7.0
     # via flake8
-mdit-py-plugins==0.4.0
+mdit-py-plugins==0.4.1
     # via myst-parser
 mdurl==0.1.2
     # via markdown-it-py
-msgspec==0.18.4 ; implementation_name != "pypy"
+msgspec==0.18.4 ; implementation_name != 'pypy'
     # via -r requirements/raw/bench.txt
-mypy==1.9.0
+mypy==1.11.1
     # via -r requirements/raw/lint.txt
 mypy-extensions==1.0.0
     # via mypy
-myst-parser==2.0.0
+myst-parser==4.0.0
     # via -r requirements/raw/doc.txt
-nodeenv==1.8.0
+nodeenv==1.9.1
     # via pre-commit
 numpy==1.26.4
     # via
     #   contourpy
     #   matplotlib
-packaging==24.0
+packaging==24.1
     # via
     #   marshmallow
     #   matplotlib
     #   plotly
     #   pytest
     #   sphinx
-pbr==6.0.0
+pbr==6.1.0
     # via sphinxcontrib-apidoc
 phonenumberslite==8.13.26
     # via -r requirements/raw/test_extra_none.txt
-pillow==10.3.0
+pillow==10.4.0
     # via matplotlib
-platformdirs==4.2.0
+platformdirs==4.2.2
     # via virtualenv
-plotly==5.21.0
+plotly==5.23.0
     # via
     #   -r requirements/raw/_bench-orchestrate.txt
     #   -r requirements/raw/doc.txt
 pluggy==1.5.0
     # via pytest
-pre-commit==3.7.0
+pre-commit==3.8.0
     # via -r requirements/raw/lint.txt
 psutil==5.9.5
     # via
     #   -r requirements/raw/bench.txt
     #   pyperf
-pycodestyle==2.11.1
+pycodestyle==2.12.1
     # via flake8
-pydantic==2.7.1
+pydantic==2.8.2
     # via
     #   -r requirements/raw/bench.txt
     #   -r requirements/raw/test_extra_new.txt
-pydantic-core==2.18.2
+pydantic-core==2.20.1
     # via pydantic
 pyflakes==3.2.0
     # via flake8
-pygments==2.17.2
+pygments==2.18.0
     # via
     #   furo
     #   sphinx
-pyparsing==3.1.2
+pyparsing==3.1.4
     # via matplotlib
 pyperf==2.6.1
     # via -r requirements/raw/bench.txt
@@ -172,15 +168,15 @@ python-dateutil==2.9.0.post0
     # via matplotlib
 pytz==2024.1
     # via dirty-equals
-pyyaml==6.0.1
+pyyaml==6.0.2
     # via
     #   myst-parser
     #   pre-commit
 radon==6.0.1
     # via -r requirements/raw/lint.txt
-requests==2.31.0
+requests==2.32.3
     # via sphinx
-ruff==0.4.1
+ruff==0.6.2
     # via -r requirements/raw/lint.txt
 schematics==2.1.1
     # via -r requirements/raw/bench.txt
@@ -192,9 +188,9 @@ smmap==5.0.1
     # via gitdb
 snowballstemmer==2.2.0
     # via sphinx
-soupsieve==2.5
+soupsieve==2.6
     # via beautifulsoup4
-sphinx==7.3.7
+sphinx==8.0.2
     # via
     #   -r requirements/raw/doc.txt
     #   furo
@@ -212,48 +208,44 @@ sphinx-better-subsection==0.2
     # via -r requirements/raw/doc.txt
 sphinx-copybutton==0.5.2
     # via -r requirements/raw/doc.txt
-sphinx-design==0.5.0
+sphinx-design==0.6.1
     # via -r requirements/raw/doc.txt
 sphinx-paramlinks==0.6.0
     # via -r requirements/raw/doc.txt
-sphinx-reredirects==0.1.3
+sphinx-reredirects==0.1.5
     # via -r requirements/raw/doc.txt
-sphinxcontrib-apidoc==0.4.0
+sphinxcontrib-apidoc==0.5.0
     # via -r requirements/raw/doc.txt
-sphinxcontrib-applehelp==1.0.8
+sphinxcontrib-applehelp==2.0.0
     # via sphinx
-sphinxcontrib-devhelp==1.0.6
+sphinxcontrib-devhelp==2.0.0
     # via sphinx
-sphinxcontrib-htmlhelp==2.0.5
+sphinxcontrib-htmlhelp==2.1.0
     # via sphinx
 sphinxcontrib-jsmath==1.0.1
     # via sphinx
-sphinxcontrib-qthelp==1.0.7
+sphinxcontrib-qthelp==2.0.0
     # via sphinx
-sphinxcontrib-serializinghtml==1.1.10
+sphinxcontrib-serializinghtml==2.0.0
     # via sphinx
 sphinxext-opengraph==0.9.1
     # via -r requirements/raw/doc.txt
-sqlalchemy==2.0.30
+sqlalchemy==2.0.32
     # via -r requirements/raw/test_extra_new.txt
-tenacity==8.2.3
+tenacity==9.0.0
     # via plotly
 toml==0.10.2
     # via vulture
-typing-extensions==4.11.0
+typing-extensions==4.12.2
     # via
     #   mashumaro
     #   mypy
     #   pydantic
     #   pydantic-core
     #   sqlalchemy
-urllib3==2.2.1
+urllib3==2.2.2
     # via requests
-virtualenv==20.25.3
+virtualenv==20.26.3
     # via pre-commit
 vulture==2.10
     # via -r requirements/raw/lint.txt
-
-# The following packages are considered to be unsafe in a requirements file:
-setuptools==69.5.1
-    # via nodeenv
diff --git a/requirements/pre.txt b/requirements/pre.txt
index 8b5a7dc2..3777381f 100644
--- a/requirements/pre.txt
+++ b/requirements/pre.txt
@@ -1,12 +1,8 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/pre.txt --strip-extras requirements/raw/pre.txt
-#
-wheel==0.43.0
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/pre.txt -o requirements/pre.txt --allow-unsafe --strip-extras --no-strip-markers
+pip==24.2
     # via -r requirements/raw/pre.txt
-
-# The following packages are considered to be unsafe in a requirements file:
-pip==24.0
+uv==0.3.2
+    # via -r requirements/raw/pre.txt
+wheel==0.44.0
     # via -r requirements/raw/pre.txt
diff --git a/requirements/raw/_bench-orchestrate.txt b/requirements/raw/_bench-orchestrate.txt
index 3dde0090..6e36001f 100644
--- a/requirements/raw/_bench-orchestrate.txt
+++ b/requirements/raw/_bench-orchestrate.txt
@@ -1,4 +1,4 @@
 -r bench.txt
 
 matplotlib==3.8.2
-plotly==5.21.0
+plotly==5.23.0
diff --git a/requirements/raw/bench.txt b/requirements/raw/bench.txt
index 07e2e63f..ff1050c8 100644
--- a/requirements/raw/bench.txt
+++ b/requirements/raw/bench.txt
@@ -6,7 +6,7 @@ pyperf==2.6.1
 psutil==5.9.5
 
 mashumaro==3.10
-pydantic==2.7.1
+pydantic==2.8.2
 cattrs==23.1.2
 schematics==2.1.1
 dataclass-factory==2.16
diff --git a/requirements/raw/bench_pypy38.txt b/requirements/raw/bench_pypy38.txt
deleted file mode 100644
index 82febbd2..00000000
--- a/requirements/raw/bench_pypy38.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-pytest==7.4.2
-
--e ./benchmarks
-
-pyperf==2.6.1
-psutil==5.9.5
-
-mashumaro==3.10
-pydantic==2.5.3   # last version with pypy38 support
-cattrs==23.1.2
-schematics==2.1.1
-dataclass-factory==2.16
-marshmallow==3.20.1
-msgspec==0.18.4; implementation_name != "pypy"
diff --git a/requirements/raw/dev.txt b/requirements/raw/dev.txt
index 6d0d6a39..47fd7a2b 100644
--- a/requirements/raw/dev.txt
+++ b/requirements/raw/dev.txt
@@ -1,4 +1,3 @@
-pip-tools==7.4.1
 setuptools==69.5.1
 towncrier==23.11.0
 
diff --git a/requirements/raw/doc.txt b/requirements/raw/doc.txt
index 97d25151..661b85b3 100644
--- a/requirements/raw/doc.txt
+++ b/requirements/raw/doc.txt
@@ -1,18 +1,18 @@
 -r bench.txt
 
-sphinx==7.3.7
+sphinx==8.0.2
 sphinx-copybutton==0.5.2
-sphinx-design==0.5.0
+sphinx-design==0.6.1
 sphinx-paramlinks==0.6.0
-myst-parser==2.0.0
+myst-parser==4.0.0
 sphinxext-opengraph==0.9.1
 sphinx-better-subsection==0.2
-sphinx-reredirects==0.1.3
+sphinx-reredirects==0.1.5
 
-sphinxcontrib-apidoc==0.4.0
-furo==2024.1.29
+sphinxcontrib-apidoc==0.5.0
+furo==2024.8.6
 
 docutils-stubs==0.0.22
 
 gitpython==3.1.43
-plotly==5.21.0
+plotly==5.23.0
diff --git a/requirements/raw/lint.txt b/requirements/raw/lint.txt
index 1fbd0546..c4583b0a 100644
--- a/requirements/raw/lint.txt
+++ b/requirements/raw/lint.txt
@@ -2,13 +2,13 @@
 -r _bench-orchestrate.txt
 -r doc.txt
 
-pre-commit==3.7.0
+pre-commit==3.8.0
 
-mypy==1.9.0
+mypy==1.11.1
 
 vulture==2.10
 
-ruff==0.4.1
+ruff==0.6.2
 
 radon==6.0.1
 dlint==0.14.1
diff --git a/requirements/raw/pre.txt b/requirements/raw/pre.txt
index c9fbbb05..833a96e3 100644
--- a/requirements/raw/pre.txt
+++ b/requirements/raw/pre.txt
@@ -1,2 +1,3 @@
-pip==24.0
-wheel==0.43.0
+pip==24.2
+wheel==0.44.0
+uv==0.3.2
diff --git a/requirements/raw/runner.txt b/requirements/raw/runner.txt
index 9187419b..e1a3825a 100644
--- a/requirements/raw/runner.txt
+++ b/requirements/raw/runner.txt
@@ -1,3 +1,3 @@
-tox==4.14.2
+tox-uv==1.11.2
 invoke==2.2.0
 coverage==7.4.4
diff --git a/requirements/runner.txt b/requirements/runner.txt
index 0df5be1e..1feda624 100644
--- a/requirements/runner.txt
+++ b/requirements/runner.txt
@@ -1,10 +1,6 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/runner.txt --strip-extras requirements/raw/runner.txt
-#
-cachetools==5.3.3
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/runner.txt -o requirements/runner.txt --allow-unsafe --strip-extras --no-strip-markers
+cachetools==5.5.0
     # via tox
 chardet==5.2.0
     # via tox
@@ -14,25 +10,30 @@ coverage==7.4.4
     # via -r requirements/raw/runner.txt
 distlib==0.3.8
     # via virtualenv
-filelock==3.13.4
+filelock==3.15.4
     # via
     #   tox
     #   virtualenv
 invoke==2.2.0
     # via -r requirements/raw/runner.txt
-packaging==24.0
+packaging==24.1
     # via
     #   pyproject-api
     #   tox
-platformdirs==4.2.0
+    #   tox-uv
+platformdirs==4.2.2
     # via
     #   tox
     #   virtualenv
 pluggy==1.5.0
     # via tox
-pyproject-api==1.6.1
+pyproject-api==1.7.1
     # via tox
-tox==4.14.2
+tox==4.18.0
+    # via tox-uv
+tox-uv==1.11.2
     # via -r requirements/raw/runner.txt
-virtualenv==20.25.3
+uv==0.4.2
+    # via tox-uv
+virtualenv==20.26.3
     # via tox
diff --git a/scripts/astpath_lint.py b/scripts/astpath_lint.py
index 9e4a9bff..6128229e 100644
--- a/scripts/astpath_lint.py
+++ b/scripts/astpath_lint.py
@@ -10,9 +10,9 @@
 import os
 import sys
 from abc import ABC, abstractmethod
+from collections.abc import Iterable
 from dataclasses import dataclass
 from fnmatch import fnmatch
-from typing import Iterable, List
 
 from astpath.search import file_to_xml_ast, find_in_ast
 
@@ -32,7 +32,7 @@ def get_exclude_patterns(self) -> Iterable[str]:
 
 
 class ImportRule(Rule):
-    def __init__(self, module: str, variable: str, error_msg: str, exclude: List[str]):
+    def __init__(self, module: str, variable: str, error_msg: str, exclude: list[str]):
         self.module = module
         self.variable = variable
         self.error_msg = error_msg
@@ -64,7 +64,11 @@ class RuleMatch:
         module="typing",
         variable="get_type_hints",
         error_msg="Use type_tools.get_all_type_hints() instead of typing.get_type_hints()",
-        exclude=["src/adaptix/_internal/type_tools/fundamentals.py"],
+        exclude=[
+            "src/adaptix/_internal/type_tools/fundamentals.py",
+            "docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/main.py",
+            "docs/examples/loading-and-dumping/extended_usage/dealing_with_type_checking/error_on_analysis.py",
+        ],
     ),
     ImportRule(
         module="_decimal",
@@ -87,7 +91,7 @@ class RuleMatch:
 ]
 
 
-def analyze_file(filename: str, rule_matches: List[RuleMatch]) -> None:
+def analyze_file(filename: str, rule_matches: list[RuleMatch]) -> None:
     xml_ast = file_to_xml_ast(filename)
 
     for rule in RULES:
@@ -119,7 +123,7 @@ def main() -> None:
     parser.add_argument("targets", help="files to lint", nargs="+")
     args = parser.parse_args()
 
-    rule_matches: List[RuleMatch] = []
+    rule_matches: list[RuleMatch] = []
 
     for target in args.targets:
         for root, _, filenames in os.walk(target):
diff --git a/scripts/invoke_tasks.py b/scripts/invoke_tasks.py
index 9b4ac524..db76c668 100644
--- a/scripts/invoke_tasks.py
+++ b/scripts/invoke_tasks.py
@@ -41,8 +41,8 @@ def cov(c: Context, env_list, output="coverage.xml", parallel=False):
 def deps_compile(c: Context, upgrade=False):
     promises = [
         c.run(
-            f'pip-compile {req} -o {Path("requirements") / req.name}'
-            ' -q --allow-unsafe --strip-extras'
+            f'uv pip compile {req} -o {Path("requirements") / req.name}'
+            ' -q --allow-unsafe --strip-extras --no-strip-markers'
             + if_str(upgrade, " --upgrade"),
             asynchronous=True,
         )
diff --git a/src/adaptix/__init__.py b/src/adaptix/__init__.py
index 0a8d6f49..3e2df882 100644
--- a/src/adaptix/__init__.py
+++ b/src/adaptix/__init__.py
@@ -1,11 +1,13 @@
 from ._internal.common import Dumper, Loader, TypeHint
 from ._internal.definitions import DebugTrail
-from ._internal.model_tools.introspection.typed_dict import TypedDictAt38Warning
 from ._internal.morphing.facade.func import dump, load
 from ._internal.morphing.facade.provider import (
     as_is_dumper,
     as_is_loader,
     constructor,
+    date_by_timestamp,
+    datetime_by_format,
+    datetime_by_timestamp,
     default_dict,
     dumper,
     enum_by_exact_value,
@@ -30,6 +32,7 @@
 from ._internal.morphing.name_layout.base import ExtraIn, ExtraOut
 from ._internal.name_style import NameStyle
 from ._internal.provider.facade.provider import bound
+from ._internal.retort.searching_retort import ProviderNotFoundError
 from ._internal.utils import Omittable, Omitted, create_deprecated_alias_getter
 from .provider import (
     AggregateCannotProvide,
@@ -42,7 +45,6 @@
     Request,
     create_loc_stack_checker,
 )
-from .retort import ProviderNotFoundError
 
 __all__ = (
     "Dumper",
@@ -64,10 +66,12 @@
     "flag_by_member_names",
     "name_mapping",
     "default_dict",
+    "datetime_by_format",
+    "date_by_timestamp",
+    "datetime_by_timestamp",
     "AdornedRetort",
     "FilledRetort",
     "Retort",
-    "TypedDictAt38Warning",
     "Omittable",
     "Omitted",
     "provider",
diff --git a/src/adaptix/_internal/code_tools/ast_templater.py b/src/adaptix/_internal/code_tools/ast_templater.py
index 31b11e26..ca413213 100644
--- a/src/adaptix/_internal/code_tools/ast_templater.py
+++ b/src/adaptix/_internal/code_tools/ast_templater.py
@@ -1,6 +1,6 @@
 import ast
 from ast import AST, NodeTransformer
-from typing import Mapping
+from collections.abc import Mapping
 
 
 class Substitutor(NodeTransformer):
diff --git a/src/adaptix/_internal/code_tools/cascade_namespace.py b/src/adaptix/_internal/code_tools/cascade_namespace.py
index 2eb57d61..cea71d77 100644
--- a/src/adaptix/_internal/code_tools/cascade_namespace.py
+++ b/src/adaptix/_internal/code_tools/cascade_namespace.py
@@ -1,5 +1,6 @@
 from abc import ABC, abstractmethod
-from typing import AbstractSet, Mapping, Optional, Set
+from collections.abc import Mapping, Set
+from typing import Optional
 
 from .utils import NAME_TO_BUILTIN
 
@@ -29,14 +30,14 @@ def __init__(
         self,
         constants: Optional[Mapping[str, object]] = None,
         outer_constants: Optional[Mapping[str, object]] = None,
-        occupied: Optional[AbstractSet[str]] = None,
+        occupied: Optional[Set[str]] = None,
         *,
         allow_builtins: bool = False,
     ):
         self._constants = {} if constants is None else dict(constants)
         self._outer_constants = {} if outer_constants is None else dict(outer_constants)
         self._occupied = set() if occupied is None else occupied
-        self._variables: Set[str] = set()
+        self._variables: set[str] = set()
         self._allow_builtins = allow_builtins
 
     def try_add_constant(self, name: str, value: object) -> bool:
diff --git a/src/adaptix/_internal/code_tools/code_builder.py b/src/adaptix/_internal/code_tools/code_builder.py
index 06a2fb4a..bc49aa6a 100644
--- a/src/adaptix/_internal/code_tools/code_builder.py
+++ b/src/adaptix/_internal/code_tools/code_builder.py
@@ -1,8 +1,9 @@
 import contextlib
 from collections import deque
+from collections.abc import Generator, Iterable, Sequence
 from itertools import islice
 from textwrap import dedent
-from typing import Deque, Generator, Iterable, Sequence, TypeVar
+from typing import TypeVar
 
 CB = TypeVar("CB", bound="CodeBuilder")
 
@@ -11,7 +12,7 @@ class CodeBuilder:
     __slots__ = ("_lines", "_current_indent", "_indent_delta")
 
     def __init__(self, indent_delta: int = 4):
-        self._lines: Deque[str] = deque()
+        self._lines: deque[str] = deque()
         self._current_indent = 0
         self._indent_delta = indent_delta
 
diff --git a/src/adaptix/_internal/code_tools/compiler.py b/src/adaptix/_internal/code_tools/compiler.py
index 8d568cce..b4283f90 100644
--- a/src/adaptix/_internal/code_tools/compiler.py
+++ b/src/adaptix/_internal/code_tools/compiler.py
@@ -2,7 +2,7 @@
 from abc import ABC, abstractmethod
 from collections import defaultdict
 from threading import Lock
-from typing import Any, Callable, Dict
+from typing import Any, Callable
 
 from .code_builder import CodeBuilder
 
@@ -16,7 +16,7 @@ def compile(
         base_id: str,
         filename_maker: Callable[[str], str],
         builder: CodeBuilder,
-        namespace: Dict[str, Any],
+        namespace: dict[str, Any],
     ) -> Callable:
         """Execute content of builder and return value that body returned (it is must be a closure).
         :param base_id: string that used to generate unique id
@@ -32,7 +32,7 @@ class ConcurrentCounter:
 
     def __init__(self) -> None:
         self._lock = Lock()
-        self._name_to_idx: Dict[str, int] = defaultdict(lambda: 0)
+        self._name_to_idx: dict[str, int] = defaultdict(lambda: 0)
 
     def generate_idx(self, name: str) -> int:
         with self._lock:
@@ -54,10 +54,10 @@ def _make_source_builder(self, builder: CodeBuilder) -> CodeBuilder:
 
         return main_builder
 
-    def _compile(self, source: str, unique_filename: str, namespace: Dict[str, Any]):
+    def _compile(self, source: str, unique_filename: str, namespace: dict[str, Any]):
         code_obj = compile(source, unique_filename, "exec")
 
-        local_namespace: Dict[str, Any] = {}
+        local_namespace: dict[str, Any] = {}
         exec(code_obj, namespace, local_namespace)  # noqa: S102
         linecache.cache[unique_filename] = (
             len(source),
@@ -78,7 +78,7 @@ def compile(
         base_id: str,
         filename_maker: Callable[[str], str],
         builder: CodeBuilder,
-        namespace: Dict[str, Any],
+        namespace: dict[str, Any],
     ) -> Callable:
         source = self._make_source_builder(builder).string()
         unique_id = self._get_unique_id(base_id)
diff --git a/src/adaptix/_internal/code_tools/utils.py b/src/adaptix/_internal/code_tools/utils.py
index e70a6b2e..dc5db8ce 100644
--- a/src/adaptix/_internal/code_tools/utils.py
+++ b/src/adaptix/_internal/code_tools/utils.py
@@ -2,7 +2,7 @@
 import builtins
 import math
 from enum import Enum
-from typing import Any, Dict, Optional
+from typing import Any, Optional
 
 BUILTIN_TO_NAME = {
     getattr(builtins, name): name
@@ -88,7 +88,7 @@ def _get_complex_literal_expr(obj: object) -> Optional[str]:  # noqa: PLR0911
     return None
 
 
-_CLS_TO_FACTORY_LITERAL: Dict[Any, str] = {
+_CLS_TO_FACTORY_LITERAL: dict[Any, str] = {
     list: "[]",
     dict: "{}",
     tuple: "()",
diff --git a/src/adaptix/_internal/common.py b/src/adaptix/_internal/common.py
index bae88ea4..ee3be802 100644
--- a/src/adaptix/_internal/common.py
+++ b/src/adaptix/_internal/common.py
@@ -1,4 +1,4 @@
-from typing import TYPE_CHECKING, Any, Callable, Tuple, Type, TypeVar, Union
+from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union
 
 K_contra = TypeVar("K_contra", contravariant=True)
 V_co = TypeVar("V_co", covariant=True)
@@ -12,9 +12,9 @@
 
 TypeHint = Any
 
-VarTuple = Tuple[T, ...]
+VarTuple = tuple[T, ...]
 
-Catchable = Union[Type[BaseException], VarTuple[Type[BaseException]]]
+Catchable = Union[type[BaseException], VarTuple[type[BaseException]]]
 
 # https://github.com/python/typing/issues/684#issuecomment-548203158
 if TYPE_CHECKING:
diff --git a/src/adaptix/_internal/compat.py b/src/adaptix/_internal/compat.py
index 23adacfb..21d52215 100644
--- a/src/adaptix/_internal/compat.py
+++ b/src/adaptix/_internal/compat.py
@@ -1,14 +1,7 @@
 try:
-    from builtins import ExceptionGroup
+    from builtins import BaseExceptionGroup, ExceptionGroup
 except ImportError:
-    from exceptiongroup import ExceptionGroup  # type: ignore[no-redef]
+    from exceptiongroup import BaseExceptionGroup, ExceptionGroup  # type: ignore[no-redef]
 
 CompatExceptionGroup = ExceptionGroup
-
-
-try:
-    from ast import unparse
-except ImportError:
-    from astunparse import unparse  # type: ignore[no-redef]
-
-compat_ast_unparse = unparse
+CompatBaseExceptionGroup = BaseExceptionGroup
diff --git a/src/adaptix/_internal/conversion/broaching/code_generator.py b/src/adaptix/_internal/conversion/broaching/code_generator.py
index 89558e82..69354f19 100644
--- a/src/adaptix/_internal/conversion/broaching/code_generator.py
+++ b/src/adaptix/_internal/conversion/broaching/code_generator.py
@@ -3,15 +3,15 @@
 from abc import ABC, abstractmethod
 from ast import AST
 from collections import defaultdict
+from collections.abc import Mapping
 from inspect import Signature
-from typing import DefaultDict, Mapping, Tuple, Union
+from typing import Union
 
 from ...code_tools.ast_templater import ast_substitute
 from ...code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
 from ...code_tools.code_builder import CodeBuilder
 from ...code_tools.name_sanitizer import NameSanitizer
 from ...code_tools.utils import get_literal_expr, get_literal_from_factory
-from ...compat import compat_ast_unparse
 from ...model_tools.definitions import DescriptorAccessor, ItemAccessor
 from ...special_cases_optimization import as_is_stub, as_is_stub_with_ctx
 from .definitions import (
@@ -37,7 +37,7 @@ class GenState:
     def __init__(self, namespace: CascadeNamespace, name_sanitizer: NameSanitizer):
         self._namespace = namespace
         self._name_sanitizer = name_sanitizer
-        self._prefix_counter: DefaultDict[str, int] = defaultdict(lambda: 0)
+        self._prefix_counter: defaultdict[str, int] = defaultdict(lambda: 0)
 
     def register_next_id(self, prefix: str, obj: object) -> str:
         number = self._prefix_counter[prefix]
@@ -59,7 +59,7 @@ def register_mangled(self, base: str, obj: object) -> str:
 
 class BroachingCodeGenerator(ABC):
     @abstractmethod
-    def produce_code(self, signature: Signature, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, signature: Signature, closure_name: str) -> tuple[str, Mapping[str, object]]:
         ...
 
 
@@ -74,7 +74,7 @@ def _create_state(self, namespace: CascadeNamespace) -> GenState:
             name_sanitizer=self._name_sanitizer,
         )
 
-    def produce_code(self, signature: Signature, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, signature: Signature, closure_name: str) -> tuple[str, Mapping[str, object]]:
         builder = CodeBuilder()
         namespace = BuiltinCascadeNamespace(occupied=signature.parameters.keys())
         state = self._create_state(namespace=namespace)
@@ -86,7 +86,7 @@ def produce_code(self, signature: Signature, closure_name: str) -> Tuple[str, Ma
         )
         with builder(f"def {closure_name}{no_types_signature}:"):
             body = self._gen_plan_element_dispatch(state, self._plan)
-            builder += "return " + compat_ast_unparse(body)
+            builder += "return " + ast.unparse(body)
 
         builder += f"{closure_name}.__signature__ = _closure_signature"
         builder += f"{closure_name}.__name__ = {closure_name!r}"
@@ -151,19 +151,19 @@ def _gen_function_call(self, state: GenState, element: FunctionElement[Broaching
                 args.append(sub_ast)
             elif isinstance(arg, KeywordArg):
                 sub_ast = self._gen_plan_element_dispatch(state, arg.element)
-                keywords.append(ast.keyword(arg=arg.key, value=sub_ast))
+                keywords.append(ast.keyword(arg=arg.key, value=sub_ast))  # type: ignore[call-overload]
             elif isinstance(arg, UnpackMapping):
                 sub_ast = self._gen_plan_element_dispatch(state, arg.element)
-                keywords.append(ast.keyword(value=sub_ast))
+                keywords.append(ast.keyword(value=sub_ast))  # type: ignore[call-overload]
             elif isinstance(arg, UnpackIterable):
                 sub_ast = self._gen_plan_element_dispatch(state, arg.element)
-                args.append(ast.Starred(value=sub_ast, ctx=ast.Load()))
+                args.append(ast.Starred(value=sub_ast, ctx=ast.Load()))  # type: ignore[arg-type]
             else:
                 raise TypeError
 
         return ast.Call(
             func=ast.Name(name, ast.Load()),
-            args=args,
+            args=args,  # type: ignore[arg-type]
             keywords=keywords,
         )
 
diff --git a/src/adaptix/_internal/conversion/coercer_provider.py b/src/adaptix/_internal/conversion/coercer_provider.py
index edc0eea2..212e1788 100644
--- a/src/adaptix/_internal/conversion/coercer_provider.py
+++ b/src/adaptix/_internal/conversion/coercer_provider.py
@@ -2,13 +2,13 @@
 from abc import ABC, abstractmethod
 from collections import deque
 from dataclasses import replace
-from typing import Any, Callable, Tuple, Union, final
+from typing import Any, Callable, Union, final
 
 from ..common import Coercer, OneArgCoercer, TypeHint
+from ..morphing.utils import try_normalize_type
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import LocStackChecker
 from ..provider.location import GenericParamLoc
-from ..provider.request_cls import try_normalize_type
 from ..special_cases_optimization import as_is_stub, as_is_stub_with_ctx
 from ..type_tools import BaseNormType, is_generic, is_parametrized, is_subclass_soft, normalize_type, strip_tags
 from .provider_template import CoercerProvider
@@ -131,23 +131,11 @@ def _provide_coercer_norm_types(
 
         not_none_src = self._get_not_none(norm_src)
         not_none_dst = self._get_not_none(norm_dst)
-        not_none_request = replace(
-            request,
-            src=request.src.append_with(
-                GenericParamLoc(
-                    type=not_none_src.source,
-                    generic_pos=0,
-                ),
-            ),
-            dst=request.dst.append_with(
-                GenericParamLoc(
-                    type=not_none_dst.source,
-                    generic_pos=0,
-                ),
-            ),
-        )
         not_none_coercer = mediator.mandatory_provide(
-            not_none_request,
+            request.append_loc(
+                src_loc=GenericParamLoc(type=not_none_src.source, generic_pos=0),
+                dst_loc=GenericParamLoc(type=not_none_dst.source, generic_pos=0),
+            ),
             lambda x: "Cannot create coercer for optionals. Coercer for wrapped value cannot be created",
         )
         if not_none_coercer == as_is_stub_with_ctx:
@@ -214,14 +202,9 @@ def _provide_coercer_norm_types(
         src_arg_tp = self._parse_source(norm_src)
         dst_factory, dst_arg_tp = self._parse_destination(norm_dst)
         element_coercer = mediator.mandatory_provide(
-            CoercerRequest(
-                src=request.src.append_with(
-                    GenericParamLoc(type=src_arg_tp, generic_pos=0),
-                ),
-                ctx=request.ctx,
-                dst=request.dst.append_with(
-                    GenericParamLoc(type=dst_arg_tp, generic_pos=0),
-                ),
+            request.append_loc(
+                src_loc=GenericParamLoc(type=src_arg_tp, generic_pos=0),
+                dst_loc=GenericParamLoc(type=dst_arg_tp, generic_pos=0),
             ),
             lambda x: "Cannot create coercer for iterables. Coercer for element cannot be created",
         )
@@ -232,14 +215,14 @@ def iterable_coercer(data, ctx):
         return iterable_coercer
 
     def _parse_source(self, norm: BaseNormType) -> TypeHint:
-        if norm.origin == tuple and norm.args[-1] != Ellipsis:
+        if norm.origin is tuple and norm.args[-1] != Ellipsis:
             raise CannotProvide("Constant-length tuple is not supported yet", is_demonstrative=True)
         if norm.origin in self.CONCRETE_ORIGINS or norm.origin in self.ABC_TO_IMPL:
             return norm.args[0].source
         raise CannotProvide
 
-    def _parse_destination(self, norm: BaseNormType) -> Tuple[Callable, TypeHint]:
-        if norm.origin == tuple and norm.args[-1] != Ellipsis:
+    def _parse_destination(self, norm: BaseNormType) -> tuple[Callable, TypeHint]:
+        if norm.origin is tuple and norm.args[-1] != Ellipsis:
             raise CannotProvide("Constant-length tuple is not supported yet", is_demonstrative=True)
         if norm.origin in self.CONCRETE_ORIGINS:
             return norm.origin, norm.args[0].source
@@ -259,26 +242,16 @@ def _provide_coercer_norm_types(
         src_key_tp, src_value_tp = self._parse_source(norm_src)
         dst_key_tp, dst_value_tp = self._parse_destination(norm_dst)
         key_coercer = mediator.mandatory_provide(
-            CoercerRequest(
-                src=request.src.append_with(
-                    GenericParamLoc(type=src_key_tp, generic_pos=0),
-                ),
-                ctx=request.ctx,
-                dst=request.dst.append_with(
-                    GenericParamLoc(type=dst_key_tp, generic_pos=0),
-                ),
+            request.append_loc(
+                src_loc=GenericParamLoc(type=src_key_tp, generic_pos=0),
+                dst_loc=GenericParamLoc(type=dst_key_tp, generic_pos=0),
             ),
             lambda x: "Cannot create coercer for dicts. Coercer for key cannot be created",
         )
         value_coercer = mediator.mandatory_provide(
-            CoercerRequest(
-                src=request.src.append_with(
-                    GenericParamLoc(type=src_value_tp, generic_pos=1),
-                ),
-                ctx=request.ctx,
-                dst=request.dst.append_with(
-                    GenericParamLoc(type=dst_value_tp, generic_pos=1),
-                ),
+            request.append_loc(
+                src_loc=GenericParamLoc(type=src_value_tp, generic_pos=1),
+                dst_loc=GenericParamLoc(type=dst_value_tp, generic_pos=1),
             ),
             lambda x: "Cannot create coercer for dicts. Coercer for value cannot be created",
         )
@@ -288,12 +261,12 @@ def dict_coercer(data, ctx):
 
         return dict_coercer
 
-    def _parse_source(self, norm: BaseNormType) -> Tuple[TypeHint, TypeHint]:
+    def _parse_source(self, norm: BaseNormType) -> tuple[TypeHint, TypeHint]:
         if norm.origin in (dict, collections.abc.Mapping, collections.abc.MutableMapping):
             return norm.args[0].source, norm.args[1].source
         raise CannotProvide
 
-    def _parse_destination(self, norm: BaseNormType) -> Tuple[TypeHint, TypeHint]:
+    def _parse_destination(self, norm: BaseNormType) -> tuple[TypeHint, TypeHint]:
         if norm.origin in (dict, collections.abc.Mapping, collections.abc.MutableMapping):
             return norm.args[0].source, norm.args[1].source
         raise CannotProvide
diff --git a/src/adaptix/_internal/conversion/converter_provider.py b/src/adaptix/_internal/conversion/converter_provider.py
index 7f49cf7a..4cdfa9f1 100644
--- a/src/adaptix/_internal/conversion/converter_provider.py
+++ b/src/adaptix/_internal/conversion/converter_provider.py
@@ -1,7 +1,8 @@
 import itertools
+from collections.abc import Mapping, Sequence
 from functools import update_wrapper
 from inspect import Parameter, Signature
-from typing import Any, Callable, Mapping, Optional, Sequence, Tuple
+from typing import Any, Callable, Optional
 
 from ..code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
 from ..code_tools.code_builder import CodeBuilder
@@ -12,8 +13,8 @@
 from ..model_tools.definitions import DefaultValue, NoDefault
 from ..morphing.model.basic_gen import compile_closure_with_globals_capturing, fetch_code_gen_hook
 from ..provider.essential import CannotProvide, Mediator
-from ..provider.location import FieldLoc
-from ..provider.request_cls import LocStack, TypeHintLoc
+from ..provider.loc_stack_filtering import LocStack
+from ..provider.location import FieldLoc, TypeHintLoc
 from .provider_template import ConverterProvider
 
 
@@ -84,7 +85,7 @@ def _produce_code(
         stub_function: Optional[Callable],
         closure_name: str,
         coercer: Coercer,
-    ) -> Tuple[str, Mapping[str, object]]:
+    ) -> tuple[str, Mapping[str, object]]:
         builder = CodeBuilder()
         namespace = BuiltinCascadeNamespace(occupied=signature.parameters.keys())
         namespace.add_outer_constant("_closure_signature", signature)
diff --git a/src/adaptix/_internal/conversion/facade/func.py b/src/adaptix/_internal/conversion/facade/func.py
index c45c2442..cf86df3d 100644
--- a/src/adaptix/_internal/conversion/facade/func.py
+++ b/src/adaptix/_internal/conversion/facade/func.py
@@ -1,4 +1,5 @@
-from typing import Any, Callable, Iterable, Optional, Type, TypeVar, overload
+from collections.abc import Iterable
+from typing import Any, Callable, Optional, TypeVar, overload
 
 from ...common import TypeHint
 from ...provider.essential import Provider
@@ -11,7 +12,7 @@
 CallableT = TypeVar("CallableT", bound=Callable)
 
 
-def convert(src_obj: Any, dst: Type[DstT], *, recipe: Iterable[Provider] = ()) -> DstT:
+def convert(src_obj: Any, dst: type[DstT], *, recipe: Iterable[Provider] = ()) -> DstT:
     """Function transforming a source object to destination.
 
     :param src_obj: A type of converter input data.
@@ -24,8 +25,8 @@ def convert(src_obj: Any, dst: Type[DstT], *, recipe: Iterable[Provider] = ()) -
 
 @overload
 def get_converter(
-    src: Type[SrcT],
-    dst: Type[DstT],
+    src: type[SrcT],
+    dst: type[DstT],
     *,
     recipe: Iterable[Provider] = (),
     name: Optional[str] = None,
diff --git a/src/adaptix/_internal/conversion/facade/retort.py b/src/adaptix/_internal/conversion/facade/retort.py
index 4895ad47..cffe6cb6 100644
--- a/src/adaptix/_internal/conversion/facade/retort.py
+++ b/src/adaptix/_internal/conversion/facade/retort.py
@@ -1,7 +1,8 @@
 import inspect
+from collections.abc import Iterable
 from functools import partial
 from inspect import Parameter, Signature
-from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Type, TypeVar, overload
+from typing import Any, Callable, Optional, TypeVar, overload
 
 from adaptix import TypeHint
 
@@ -61,12 +62,18 @@ class FilledConversionRetort(OperatingRetort):
 class AdornedConversionRetort(OperatingRetort):
     def _calculate_derived(self) -> None:
         super()._calculate_derived()
-        self._simple_converter_cache: Dict[Tuple[TypeHint, TypeHint, Optional[str]], Converter] = {}
+        self._simple_converter_cache: dict[tuple[TypeHint, TypeHint, Optional[str]], Converter] = {}
+
+    def replace(self: AR, *, hide_traceback: Optional[bool] = None) -> AR:
+        with self._clone() as clone:
+            if hide_traceback is not None:
+                clone._hide_traceback = hide_traceback
+        return clone
 
     def extend(self: AR, *, recipe: Iterable[Provider]) -> AR:
         with self._clone() as clone:
-            clone._inc_instance_recipe = (
-                tuple(recipe) + clone._inc_instance_recipe
+            clone._instance_recipe = (
+                tuple(recipe) + clone._instance_recipe
             )
 
         return clone
@@ -99,8 +106,8 @@ def _make_simple_converter(self, src: TypeHint, dst: TypeHint, name: Optional[st
     @overload
     def get_converter(
         self,
-        src: Type[SrcT],
-        dst: Type[DstT],
+        src: type[SrcT],
+        dst: type[DstT],
         *,
         recipe: Iterable[Provider] = (),
     ) -> Callable[[SrcT], DstT]:
@@ -169,7 +176,7 @@ def impl_converter(self, stub_function: Optional[Callable] = None, *, recipe: It
             function_name=None,
         )
 
-    def convert(self, src_obj: Any, dst: Type[DstT], *, recipe: Iterable[Provider] = ()) -> DstT:
+    def convert(self, src_obj: Any, dst: type[DstT], *, recipe: Iterable[Provider] = ()) -> DstT:
         """Method transforming a source object to destination.
 
         :param src_obj: A type of converter input data.
diff --git a/src/adaptix/_internal/conversion/linking_provider.py b/src/adaptix/_internal/conversion/linking_provider.py
index 043cd1a0..55ec720b 100644
--- a/src/adaptix/_internal/conversion/linking_provider.py
+++ b/src/adaptix/_internal/conversion/linking_provider.py
@@ -1,5 +1,6 @@
 import itertools
-from typing import Callable, Iterable, Mapping, NoReturn, Optional, TypeVar, Union
+from collections.abc import Iterable, Mapping
+from typing import Callable, NoReturn, Optional, TypeVar, Union
 
 from ..common import Coercer, OneArgCoercer, VarTuple
 from ..model_tools.definitions import DefaultFactory, DefaultValue, InputField, InputShape, Param, ParamKind
@@ -7,8 +8,8 @@
 from ..provider.essential import CannotProvide, Mediator, mandatory_apply_by_iterable
 from ..provider.fields import input_field_to_loc
 from ..provider.loc_stack_filtering import LocStackChecker
+from ..provider.loc_stack_tools import format_loc_stack
 from ..provider.location import FieldLoc
-from ..provider.request_cls import format_loc_stack
 from ..utils import add_note
 from .provider_template import LinkingProvider
 from .request_cls import (
diff --git a/src/adaptix/_internal/conversion/model_coercer_provider.py b/src/adaptix/_internal/conversion/model_coercer_provider.py
index 0189ddcd..13f5d526 100644
--- a/src/adaptix/_internal/conversion/model_coercer_provider.py
+++ b/src/adaptix/_internal/conversion/model_coercer_provider.py
@@ -1,5 +1,6 @@
+from collections.abc import Iterable, Mapping
 from inspect import Parameter, Signature
-from typing import Callable, Iterable, List, Mapping, Optional, Tuple, Union
+from typing import Callable, Optional, Union
 
 from ..code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ..code_tools.name_sanitizer import BuiltinNameSanitizer, NameSanitizer
@@ -28,10 +29,10 @@
 )
 from ..model_tools.definitions import DefaultValue, InputField, InputShape, OutputShape, ParamKind, create_key_accessor
 from ..morphing.model.basic_gen import compile_closure_with_globals_capturing, fetch_code_gen_hook
-from ..provider.essential import CannotProvide, Mediator, mandatory_apply_by_iterable
+from ..provider.essential import AggregateCannotProvide, CannotProvide, Mediator, mandatory_apply_by_iterable
 from ..provider.fields import input_field_to_loc, output_field_to_loc
+from ..provider.loc_stack_filtering import LocStack
 from ..provider.location import AnyLoc, InputFieldLoc, InputFuncFieldLoc, OutputFieldLoc
-from ..provider.request_cls import LocStack
 from ..provider.shape_provider import InputShapeRequest, OutputShapeRequest, provide_generic_resolved_shape
 from ..utils import add_note
 from .provider_template import CoercerProvider
@@ -42,8 +43,7 @@ def __init__(self, *, name_sanitizer: NameSanitizer = BuiltinNameSanitizer()):
         self._name_sanitizer = name_sanitizer
 
     def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
-        dst_shape = self._fetch_dst_shape(mediator, request.dst)
-        src_shape = self._fetch_src_shape(mediator, request.src)
+        dst_shape, src_shape = self._fetch_shapes(mediator, request)
         broaching_plan = self._make_broaching_plan(
             mediator=mediator,
             request=request,
@@ -52,6 +52,32 @@ def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coerc
         )
         return self._make_coercer(mediator, request, broaching_plan)
 
+    def _fetch_shapes(self, mediator: Mediator, request: CoercerRequest) -> tuple[InputShape, OutputShape]:
+        exception_and_type_list = []
+        try:
+            dst_shape = self._fetch_dst_shape(mediator, request.dst)
+        except CannotProvide as e:
+            exception_and_type_list.append((e, request.dst.last.type))
+
+        try:
+            src_shape = self._fetch_src_shape(mediator, request.src)
+        except CannotProvide as e:
+            exception_and_type_list.append((e, request.src.last.type))
+
+        if len(exception_and_type_list) == 1:
+            raise CannotProvide(
+                parent_notes_gen=lambda: [
+                    f"Hint: Class `{exception_and_type_list[0][1].__name__}` is not recognized as model."
+                    " Did your forget `@dataclass` decorator? Check documentation what model kinds are supported",
+                ],
+            )
+        if len(exception_and_type_list) == 2:  # noqa: PLR2004
+            raise AggregateCannotProvide(
+                "Classes are not recognized as models",
+                [exc for exc, tp in exception_and_type_list],
+            )
+        return dst_shape, src_shape
+
     def _make_coercer(
         self,
         mediator: Mediator,
@@ -118,13 +144,13 @@ def _fetch_linkings(
         request: CoercerRequest,
         dst_shape: InputShape,
         src_shape: OutputShape,
-    ) -> Iterable[Tuple[InputField, Optional[LinkingResult]]]:
+    ) -> Iterable[tuple[InputField, Optional[LinkingResult]]]:
         sources = tuple(
             request.src.append_with(output_field_to_loc(src_field))
             for src_field in src_shape.fields
         )
 
-        def fetch_field_linking(dst_field: InputField) -> Tuple[InputField, Optional[LinkingResult]]:
+        def fetch_field_linking(dst_field: InputField) -> tuple[InputField, Optional[LinkingResult]]:
             destination = request.dst.append_with(input_field_to_loc(dst_field))
             try:
                 linking = mediator.provide(
@@ -226,7 +252,7 @@ def _generate_function_linking_to_sub_plan(
         request: CoercerRequest,
         linking: FunctionLinking,
     ) -> BroachingPlan:
-        args: List[FuncCallArg[BroachingPlan]] = []
+        args: list[FuncCallArg[BroachingPlan]] = []
         field_to_sub_plan = self._generate_sub_plan(
             mediator,
             request,
@@ -249,7 +275,7 @@ def _generate_sub_plan(
         self,
         mediator: Mediator,
         request: CoercerRequest,
-        field_linkings: Iterable[Tuple[InputField, LinkingResult]],
+        field_linkings: Iterable[tuple[InputField, LinkingResult]],
         parent_func: Optional[Callable],
     ) -> Mapping[InputField, BroachingPlan]:
         def generate_sub_plan(input_field: InputField, linking_result: LinkingResult):
@@ -328,7 +354,7 @@ def _make_constructor_call(
         field_to_linking: Mapping[InputField, Optional[LinkingResult]],
         field_to_sub_plan: Mapping[InputField, BroachingPlan],
     ) -> BroachingPlan:
-        args: List[FuncCallArg[BroachingPlan]] = []
+        args: list[FuncCallArg[BroachingPlan]] = []
         has_skipped_params = False
         for param in dst_shape.params:
             field = dst_shape.fields_dict[param.field_id]
diff --git a/src/adaptix/_internal/conversion/policy_provider.py b/src/adaptix/_internal/conversion/policy_provider.py
index 4c1ec800..82ea46d7 100644
--- a/src/adaptix/_internal/conversion/policy_provider.py
+++ b/src/adaptix/_internal/conversion/policy_provider.py
@@ -1,13 +1,13 @@
 from ..provider.essential import Mediator
-from ..provider.static_provider import StaticProvider, static_provision_action
+from ..provider.methods_provider import MethodsProvider, method_handler
 from .request_cls import UnlinkedOptionalPolicy, UnlinkedOptionalPolicyRequest
 
 
-class UnlinkedOptionalPolicyProvider(StaticProvider):
+class UnlinkedOptionalPolicyProvider(MethodsProvider):
     def __init__(self, *, is_allowed: bool):
         self._is_allowed = is_allowed
 
-    @static_provision_action
+    @method_handler
     def _unlinked_optional_policy(
         self,
         mediator: Mediator,
diff --git a/src/adaptix/_internal/conversion/provider_template.py b/src/adaptix/_internal/conversion/provider_template.py
index 2f015336..a7f7d186 100644
--- a/src/adaptix/_internal/conversion/provider_template.py
+++ b/src/adaptix/_internal/conversion/provider_template.py
@@ -3,13 +3,13 @@
 
 from ..common import Coercer, Converter
 from ..provider.essential import Mediator
-from ..provider.static_provider import StaticProvider, static_provision_action
+from ..provider.methods_provider import MethodsProvider, method_handler
 from .request_cls import CoercerRequest, ConverterRequest, LinkingRequest, LinkingResult
 
 
-class ConverterProvider(StaticProvider, ABC):
+class ConverterProvider(MethodsProvider, ABC):
     @final
-    @static_provision_action
+    @method_handler
     def _outer_provide_converter(self, mediator: Mediator, request: ConverterRequest):
         return self._provide_converter(mediator, request)
 
@@ -18,15 +18,15 @@ def _provide_converter(self, mediator: Mediator, request: ConverterRequest) -> C
         ...
 
 
-class CoercerProvider(StaticProvider, ABC):
-    @static_provision_action
+class CoercerProvider(MethodsProvider, ABC):
+    @method_handler
     @abstractmethod
     def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
         ...
 
 
-class LinkingProvider(StaticProvider, ABC):
-    @static_provision_action
+class LinkingProvider(MethodsProvider, ABC):
+    @method_handler
     @abstractmethod
     def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
         ...
diff --git a/src/adaptix/_internal/conversion/request_cls.py b/src/adaptix/_internal/conversion/request_cls.py
index 1ad526f1..43bbfc54 100644
--- a/src/adaptix/_internal/conversion/request_cls.py
+++ b/src/adaptix/_internal/conversion/request_cls.py
@@ -1,12 +1,13 @@
-from dataclasses import dataclass, field
+from dataclasses import dataclass, field, replace
 from inspect import Signature
-from typing import Callable, Optional, Union
+from typing import Callable, Optional, TypeVar, Union
 
 from ..common import Coercer, VarTuple
 from ..model_tools.definitions import DefaultFactory, DefaultValue, InputField, ParamKind
 from ..provider.essential import Request
+from ..provider.loc_stack_filtering import LocStack
+from ..provider.located_request import LocatedRequest
 from ..provider.location import FieldLoc, GenericParamLoc, InputFieldLoc, InputFuncFieldLoc, OutputFieldLoc, TypeHintLoc
-from ..provider.request_cls import LocatedRequest, LocStack
 
 
 @dataclass(frozen=True)
@@ -72,12 +73,18 @@ class LinkingRequest(Request[LinkingResult]):
     destination: LocStack[ConversionDestItem]
 
 
+CR = TypeVar("CR", bound="CoercerRequest")
+
+
 @dataclass(frozen=True)
 class CoercerRequest(Request[Coercer]):
     src: LocStack[ConversionSourceItem]
     ctx: ConversionContext
     dst: LocStack[ConversionDestItem]
 
+    def append_loc(self: CR, *, src_loc: ConversionSourceItem, dst_loc: ConversionDestItem) -> CR:
+        return replace(self, src=self.src.append_with(src_loc), dst=self.dst.append_with(dst_loc))
+
 
 @dataclass(frozen=True)
 class UnlinkedOptionalPolicy:
diff --git a/src/adaptix/_internal/conversion/request_filtering.py b/src/adaptix/_internal/conversion/request_filtering.py
index ffc58973..a6598024 100644
--- a/src/adaptix/_internal/conversion/request_filtering.py
+++ b/src/adaptix/_internal/conversion/request_filtering.py
@@ -1,5 +1,4 @@
-from adaptix._internal.provider.loc_stack_filtering import DirectMediator, LocStackChecker
-from adaptix._internal.provider.request_cls import LocStack
+from adaptix._internal.provider.loc_stack_filtering import DirectMediator, LocStack, LocStackChecker
 
 
 class FromCtxParam(LocStackChecker):
diff --git a/src/adaptix/_internal/datastructures.py b/src/adaptix/_internal/datastructures.py
index a4ea8ff5..b22d25d3 100644
--- a/src/adaptix/_internal/datastructures.py
+++ b/src/adaptix/_internal/datastructures.py
@@ -1,28 +1,20 @@
-from itertools import islice
-from typing import (
-    AbstractSet,
-    Callable,
+from collections.abc import (
     Collection,
-    Dict,
-    Generic,
     Hashable,
     Iterable,
     Iterator,
     KeysView,
     Mapping,
-    Optional,
-    Protocol,
     Reversible,
+    Set,
     Sized,
-    Tuple,
-    Type,
-    TypeVar,
-    Union,
     ValuesView,
-    runtime_checkable,
 )
+from itertools import islice
+from typing import Callable, Generic, Optional, Protocol, TypeVar, Union, runtime_checkable
 
 from .common import VarTuple
+from .utils import MappingHashWrapper
 
 K = TypeVar("K", bound=Hashable)
 V = TypeVar("V")
@@ -38,7 +30,7 @@ def __getitem__(self, __key: K) -> _VT_co:
         ...
 
 
-class UnrewritableDict(Dict[K, V], Generic[K, V]):
+class UnrewritableDict(dict[K, V], Generic[K, V]):
     def __setitem__(self, key, value):
         if key in self:
             old_value = self[key]
@@ -78,10 +70,10 @@ class ClassDispatcher(Generic[K_co, V]):
     """
     __slots__ = ("_mapping",)
 
-    def __init__(self, mapping: Optional[Mapping[Type[K_co], V]] = None):
-        self._mapping: Dict[Type[K_co], V] = {} if mapping is None else dict(mapping)
+    def __init__(self, mapping: Optional[Mapping[type[K_co], V]] = None):
+        self._mapping: dict[type[K_co], V] = {} if mapping is None else dict(mapping)
 
-    def dispatch(self, key: Type[K_co]) -> V:
+    def dispatch(self, key: type[K_co]) -> V:
         """Returns a value associated with the key.
         If the key does not exist, it will return
         the value of the closest superclass otherwise raise KeyError
@@ -100,13 +92,13 @@ def values(self) -> Collection[V]:
     def keys(self) -> "ClassDispatcherKeysView[K_co]":
         return ClassDispatcherKeysView(self._mapping.keys())
 
-    def items(self) -> Collection[Tuple[Type[K_co], V]]:
+    def items(self) -> Collection[tuple[type[K_co], V]]:
         return self._mapping.items()
 
     def __repr__(self):
         return f"{type(self).__qualname__}({self._mapping})"
 
-    def to_dict(self) -> Dict[Type[K_co], V]:
+    def to_dict(self) -> dict[type[K_co], V]:
         return self._mapping.copy()
 
     def __eq__(self, other):
@@ -114,14 +106,17 @@ def __eq__(self, other):
             return self._mapping == other._mapping
         return NotImplemented
 
+    def __hash__(self):
+        return hash(MappingHashWrapper(self._mapping))
+
 
 # It's not a KeysView because __iter__ of KeysView must returns an Iterator[K_co]
-# but there is no inverse of Type[]
+# but there is no inverse of type[]
 
 class ClassDispatcherKeysView(Generic[K_co]):
     __slots__ = ("_keys",)
 
-    def __init__(self, keys: AbstractSet[Type[K_co]]):
+    def __init__(self, keys: Set[type[K_co]]):
         self._keys = keys
 
     def bind(self, value: V) -> ClassDispatcher[K_co, V]:
@@ -133,7 +128,7 @@ def bind(self, value: V) -> ClassDispatcher[K_co, V]:
     def __len__(self) -> int:
         return len(self._keys)
 
-    def __iter__(self) -> Iterator[Type[K_co]]:
+    def __iter__(self) -> Iterator[type[K_co]]:
         return iter(self._keys)
 
     def __contains__(self, element: object) -> bool:
@@ -153,16 +148,16 @@ class ClassMap(Generic[H]):
 
     def __init__(self, *values: H):
         # need stable order for hash calculation
-        self._mapping: Mapping[Type[H], H] = {
+        self._mapping: Mapping[type[H], H] = {
             type(value): value
             for value in sorted(values, key=lambda v: type(v).__qualname__)
         }
         self._hash = hash(tuple(self._mapping.values()))
 
-    def __getitem__(self, item: Type[D]) -> D:
+    def __getitem__(self, item: type[D]) -> D:
         return self._mapping[item]  # type: ignore[index,return-value]
 
-    def __iter__(self) -> Iterator[Type[H]]:
+    def __iter__(self) -> Iterator[type[H]]:
         return iter(self._mapping)
 
     def __len__(self) -> int:
@@ -171,20 +166,20 @@ def __len__(self) -> int:
     def __contains__(self, item):
         return item in self._mapping
 
-    def has(self, *classes: Type[H]) -> bool:
+    def has(self, *classes: type[H]) -> bool:
         return all(key in self._mapping for key in classes)
 
     def get_or_raise(
         self,
-        key: Type[D],
-        exception_factory: Callable[[], Union[BaseException, Type[BaseException]]],
+        key: type[D],
+        exception_factory: Callable[[], Union[BaseException, type[BaseException]]],
     ) -> D:
         try:
             return self._mapping[key]  # type: ignore[index,return-value]
         except KeyError:
             raise exception_factory() from None
 
-    def keys(self) -> KeysView[Type[H]]:
+    def keys(self) -> KeysView[type[H]]:
         return self._mapping.keys()
 
     def values(self) -> ValuesView[H]:
@@ -210,7 +205,7 @@ def __repr__(self):
     def add(self: CM, *values: H) -> CM:
         return type(self)(*self._mapping.values(), *values)
 
-    def discard(self: CM, *classes: Type[H]) -> CM:
+    def discard(self: CM, *classes: type[H]) -> CM:
         return type(self)(
             value for key, value in self._mapping.items()
             if key not in classes
@@ -228,13 +223,13 @@ def __init__(self, *args: T_co):
         self._tuple = args
 
     @classmethod
-    def _from_tuple(cls: Type[StackT], tpl: VarTuple[T_co]) -> StackT:
+    def _from_tuple(cls: type[StackT], tpl: VarTuple[T_co]) -> StackT:
         self = cls.__new__(cls)
         self._tuple = tpl
         return self
 
     @classmethod
-    def from_iter(cls: Type[StackT], iterable: Iterable[T_co]) -> StackT:
+    def from_iter(cls: type[StackT], iterable: Iterable[T_co]) -> StackT:
         return cls._from_tuple(tuple(iterable))
 
     @property
diff --git a/src/adaptix/_internal/definitions.py b/src/adaptix/_internal/definitions.py
index 09443ec3..f12cd9fd 100644
--- a/src/adaptix/_internal/definitions.py
+++ b/src/adaptix/_internal/definitions.py
@@ -5,3 +5,8 @@ class DebugTrail(Enum):
     DISABLE = "DISABLE"
     FIRST = "FIRST"
     ALL = "ALL"
+
+
+class Direction(Enum):
+    INPUT = "INPUT"
+    OUTPUT = "OUTPUT"
diff --git a/src/adaptix/_internal/feature_requirement.py b/src/adaptix/_internal/feature_requirement.py
index ea10c9cb..613f06e8 100644
--- a/src/adaptix/_internal/feature_requirement.py
+++ b/src/adaptix/_internal/feature_requirement.py
@@ -3,7 +3,8 @@
 import re
 import sys
 from abc import ABC, abstractmethod
-from typing import Any, Iterable
+from collections.abc import Iterable
+from typing import Any
 
 from .common import VarTuple
 
@@ -17,11 +18,11 @@ def _false():
 
 
 class Requirement(ABC):
-    __slots__ = ("is_meet", "__bool__", "__dict__")
+    __slots__ = ("is_met", "__bool__", "__dict__")
 
     def __init__(self):
-        self.is_meet = self._evaluate()
-        self.__bool__ = _true if self.is_meet else _false
+        self.is_met = self._evaluate()
+        self.__bool__ = _true if self.is_met else _false
 
     @abstractmethod
     def _evaluate(self) -> bool:
@@ -148,10 +149,6 @@ def fail_reason(self) -> str:
         return f"{self.implementation_name} is required"
 
 
-HAS_PY_39 = PythonVersionRequirement((3, 9))
-HAS_ANNOTATED = HAS_PY_39
-HAS_STD_CLASSES_GENERICS = HAS_PY_39
-
 HAS_PY_310 = PythonVersionRequirement((3, 10))
 HAS_TYPE_UNION_OP = HAS_PY_310
 HAS_TYPE_GUARD = HAS_PY_310
diff --git a/src/adaptix/_internal/integrations/pydantic/native.py b/src/adaptix/_internal/integrations/pydantic/native.py
index 5f2c3dd7..e2cb682e 100644
--- a/src/adaptix/_internal/integrations/pydantic/native.py
+++ b/src/adaptix/_internal/integrations/pydantic/native.py
@@ -1,4 +1,5 @@
-from typing import Any, Callable, Dict, Literal, Mapping, Optional, TypeVar, Union
+from collections.abc import Mapping
+from typing import Any, Callable, Literal, Optional, TypeVar, Union
 
 from ...common import Dumper, Loader
 from ...morphing.load_error import LoadError
@@ -33,7 +34,7 @@ def __init__(
     def _skip_omitted(self, mapping: Mapping[str, T]) -> Mapping[str, T]:
         return {k: v for k, v in mapping.items() if v != Omitted()}
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         validation_params = self._skip_omitted(self._validation_params)
         validator = TypeAdapter(request.last_loc.type, config=self._config).validator.validate_python
 
@@ -54,7 +55,7 @@ def native_pydantic_loader(data):
 
         return native_pydantic_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         serialization_params = self._skip_omitted(self._serialization_params)
         serializer = TypeAdapter(request.last_loc.type, config=self._config).serializer.to_python
 
@@ -85,7 +86,7 @@ def native_pydantic(
     fallback: Omittable[Callable[[Any], Any]] = Omitted(),
     serialize_as_any: Omittable[bool] = Omitted(),
     # common parameters
-    context: Omittable[Optional[Dict[str, Any]]] = Omitted(),
+    context: Omittable[Optional[dict[str, Any]]] = Omitted(),
     config: Optional["ConfigDict"] = None,
 ) -> Provider:
     """Provider that represents value via pydantic.
diff --git a/src/adaptix/_internal/integrations/sqlalchemy/orm.py b/src/adaptix/_internal/integrations/sqlalchemy/orm.py
index 61fe3d0a..c16462d0 100644
--- a/src/adaptix/_internal/integrations/sqlalchemy/orm.py
+++ b/src/adaptix/_internal/integrations/sqlalchemy/orm.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, Type, Union
+from typing import Any, Optional, Union
 
 from sqlalchemy import JSON, Dialect, TypeDecorator, null
 from sqlalchemy.dialects import postgresql
@@ -37,7 +37,7 @@ def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
             return self._custom_impl
         return to_instance(self._load_default_dialect_impl(dialect))
 
-    def _load_default_dialect_impl(self, dialect: Dialect) -> Union[TypeEngine[Any], Type[TypeEngine[Any]]]:
+    def _load_default_dialect_impl(self, dialect: Dialect) -> Union[TypeEngine[Any], type[TypeEngine[Any]]]:
         if isinstance(dialect, PGDialect):
             return postgresql.JSONB
         return JSON
diff --git a/src/adaptix/_internal/model_tools/definitions.py b/src/adaptix/_internal/model_tools/definitions.py
index fca1492e..6fb4d4e3 100644
--- a/src/adaptix/_internal/model_tools/definitions.py
+++ b/src/adaptix/_internal/model_tools/definitions.py
@@ -1,7 +1,8 @@
 from abc import ABC, abstractmethod
+from collections.abc import Hashable, Mapping
 from dataclasses import dataclass, field
 from enum import Enum
-from typing import Any, Callable, FrozenSet, Generic, Hashable, Mapping, Optional, TypeVar, Union
+from typing import Any, Callable, Generic, Optional, TypeVar, Union
 
 from ..common import Catchable, TypeHint, VarTuple
 from ..feature_requirement import DistributionRequirement, DistributionVersionRequirement
@@ -193,7 +194,7 @@ class BaseShape:
     See doc :class InputShape: and :class OutputShape: for more details
     """
     fields: VarTuple[BaseField]
-    overriden_types: FrozenSet[str]
+    overriden_types: frozenset[str]
     fields_dict: Mapping[str, BaseField] = field(init=False, hash=False, repr=False, compare=False)
 
     def _validate(self):
diff --git a/src/adaptix/_internal/model_tools/introspection/callable.py b/src/adaptix/_internal/model_tools/introspection/callable.py
index 68b7b625..0823b643 100644
--- a/src/adaptix/_internal/model_tools/introspection/callable.py
+++ b/src/adaptix/_internal/model_tools/introspection/callable.py
@@ -2,7 +2,7 @@
 import typing
 from inspect import Parameter, Signature
 from types import MappingProxyType
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Optional
 
 from ...common import VarTuple
 from ...feature_requirement import HAS_PY_312
@@ -20,7 +20,7 @@
 )
 from .typed_dict import get_typed_dict_shape
 
-_PARAM_KIND_CONV: Dict[Any, ParamKind] = {
+_PARAM_KIND_CONV: dict[Any, ParamKind] = {
     Parameter.POSITIONAL_ONLY: ParamKind.POS_ONLY,
     Parameter.POSITIONAL_OR_KEYWORD: ParamKind.POS_OR_KW,
     Parameter.KEYWORD_ONLY: ParamKind.KW_ONLY,
@@ -33,7 +33,7 @@ def _is_empty(value):
 
 def _upack_typed_dict_kwargs(
     param_kwargs: Optional[ParamKwargs],
-) -> Tuple[VarTuple[InputField], VarTuple[Param], Optional[ParamKwargs]]:
+) -> tuple[VarTuple[InputField], VarTuple[Param], Optional[ParamKwargs]]:
     if not HAS_PY_312 or param_kwargs is None:
         return (), (), param_kwargs
 
diff --git a/src/adaptix/_internal/model_tools/introspection/dataclass.py b/src/adaptix/_internal/model_tools/introspection/dataclass.py
index cdcf76e4..a0f56e4c 100644
--- a/src/adaptix/_internal/model_tools/introspection/dataclass.py
+++ b/src/adaptix/_internal/model_tools/introspection/dataclass.py
@@ -1,6 +1,5 @@
 import inspect
 from dataclasses import MISSING as DC_MISSING, Field as DCField, fields as dc_fields, is_dataclass
-from typing import Dict
 
 from ...feature_requirement import HAS_PY_310
 from ...type_tools import get_all_type_hints, is_class_var, normalize_type
@@ -22,7 +21,7 @@
 )
 
 
-def all_dc_fields(cls) -> Dict[str, DCField]:
+def all_dc_fields(cls) -> dict[str, DCField]:
     """Builtin introspection function hides
     some fields like InitVar or ClassVar.
     That function returns full dict
diff --git a/src/adaptix/_internal/model_tools/introspection/pydantic.py b/src/adaptix/_internal/model_tools/introspection/pydantic.py
index 0a599c48..934143a1 100644
--- a/src/adaptix/_internal/model_tools/introspection/pydantic.py
+++ b/src/adaptix/_internal/model_tools/introspection/pydantic.py
@@ -1,9 +1,9 @@
 import inspect
 import itertools
-import typing
+from collections.abc import Sequence
 from functools import cached_property
 from inspect import Parameter, Signature
-from typing import Any, Callable, Optional, Protocol, Sequence, Type
+from typing import Annotated, Any, Callable, Optional, Protocol
 
 try:
     from pydantic import AliasChoices, BaseModel
@@ -14,7 +14,7 @@
 
 from adaptix import TypeHint
 
-from ...feature_requirement import HAS_ANNOTATED, HAS_PYDANTIC_PKG, HAS_SUPPORTED_PYDANTIC_PKG
+from ...feature_requirement import HAS_PYDANTIC_PKG, HAS_SUPPORTED_PYDANTIC_PKG
 from ...type_tools import get_all_type_hints, is_pydantic_class
 from ..definitions import (
     ClarifiedIntrospectionError,
@@ -57,7 +57,7 @@ def _get_default(field: WithDefaults) -> Default:
 }
 
 
-def _get_config_value(tp: "Type[BaseModel]", key: str) -> Any:
+def _get_config_value(tp: "type[BaseModel]", key: str) -> Any:
     try:
         return tp.model_config[key]  # type: ignore[literal-required]
     except KeyError:
@@ -66,7 +66,7 @@ def _get_config_value(tp: "Type[BaseModel]", key: str) -> Any:
     return _config_defaults[key]
 
 
-def _get_field_parameters(tp: "Type[BaseModel]", field_name: str, field_info: "FieldInfo") -> Sequence[str]:
+def _get_field_parameters(tp: "type[BaseModel]", field_name: str, field_info: "FieldInfo") -> Sequence[str]:
     # AliasPath is ignored
     if field_info.validation_alias is None:
         parameters = [field_name]
@@ -79,7 +79,7 @@ def _get_field_parameters(tp: "Type[BaseModel]", field_name: str, field_info: "F
     return [param for param in parameters if param.isidentifier()]
 
 
-def _get_field_parameter_name(tp: "Type[BaseModel]", field_name: str, field_info: "FieldInfo") -> str:
+def _get_field_parameter_name(tp: "type[BaseModel]", field_name: str, field_info: "FieldInfo") -> str:
     parameters = _get_field_parameters(tp, field_name, field_info)
     if not parameters:
         raise ClarifiedIntrospectionError(
@@ -102,12 +102,12 @@ def _signature_is_self_with_kwargs_only(init_signature: Signature) -> bool:
 
 
 def _get_field_type(field_info: "FieldInfo") -> TypeHint:
-    if field_info.metadata and HAS_ANNOTATED:
-        return typing.Annotated[(field_info.annotation, *field_info.metadata)]
+    if field_info.metadata:
+        return Annotated[(field_info.annotation, *field_info.metadata)]
     return field_info.annotation
 
 
-def _get_input_shape(tp: "Type[BaseModel]") -> InputShape:
+def _get_input_shape(tp: "type[BaseModel]") -> InputShape:
     if not _signature_is_self_with_kwargs_only(inspect.signature(tp.__init__)):
         raise ClarifiedIntrospectionError(
             "Pydantic model `__init__` must takes only self and one variable keyword parameter",
@@ -163,7 +163,7 @@ def _get_computed_field_type(field_id: str, computed_field_info: "ComputedFieldI
     return signature.return_annotation
 
 
-def _get_output_shape(tp: "Type[BaseModel]") -> OutputShape:
+def _get_output_shape(tp: "type[BaseModel]") -> OutputShape:
     type_hints = get_all_type_hints(tp)
     fields = itertools.chain(
         (
diff --git a/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py b/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
index d29056cb..e8cb4674 100644
--- a/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
+++ b/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
@@ -1,5 +1,6 @@
 import inspect
-from typing import Any, Generic, List, Mapping, Optional, TypeVar
+from collections.abc import Mapping
+from typing import Any, Generic, Optional, TypeVar
 
 from ...common import TypeHint
 
@@ -86,7 +87,7 @@ def _get_type_for_relationship(relationship: "RelationshipProperty", type_hints:
         return _unwrap_mapped_annotation(type_hints[relationship.key])
     except KeyError:
         if relationship.uselist:
-            return List[relationship.entity.class_]  # type: ignore[name-defined]
+            return list[relationship.entity.class_]  # type: ignore[name-defined]
         return Optional[relationship.entity.class_]
 
 
@@ -153,7 +154,7 @@ def _get_input_shape(
         )
 
     for relationship in relationships:
-        if relationship.collection_class is not None and strip_alias(relationship.collection_class) != list:
+        if relationship.collection_class is not None and strip_alias(relationship.collection_class) is not list:
             continue  # it is not supported
         if relationship.uselist is None:
             continue  # it cannot be None there
@@ -203,7 +204,7 @@ def _get_output_shape(
         if isinstance(column, sqlalchemy.Column)
     ]
     for relationship in relationships:
-        if relationship.collection_class is not None and strip_alias(relationship.collection_class) != list:
+        if relationship.collection_class is not None and strip_alias(relationship.collection_class) is not list:
             continue  # it is not supported
         if relationship.uselist is None:
             continue  # it cannot be None there
diff --git a/src/adaptix/_internal/model_tools/introspection/typed_dict.py b/src/adaptix/_internal/model_tools/introspection/typed_dict.py
index bcc67ec7..19ab8d30 100644
--- a/src/adaptix/_internal/model_tools/introspection/typed_dict.py
+++ b/src/adaptix/_internal/model_tools/introspection/typed_dict.py
@@ -1,9 +1,9 @@
 import typing
-import warnings
+from collections.abc import Sequence, Set
 from types import MappingProxyType
-from typing import AbstractSet, Sequence, Set, Tuple
+from typing import Annotated
 
-from ...feature_requirement import HAS_PY_39, HAS_TYPED_DICT_REQUIRED
+from ...feature_requirement import HAS_TYPED_DICT_REQUIRED
 from ...type_tools import BaseNormType, get_all_type_hints, is_typed_dict_class, normalize_type
 from ..definitions import (
     FullShape,
@@ -20,18 +20,6 @@
 )
 
 
-class TypedDictAt38Warning(UserWarning):
-    """Runtime introspection of TypedDict at python3.8 does not support inheritance.
-    Please update python or consider limitations suppressing this warning
-    """
-
-    def __str__(self):
-        return (
-            "Runtime introspection of TypedDict at python3.8 does not support inheritance."
-            " Please, update python or consider limitations suppressing this warning"
-        )
-
-
 def _get_td_hints(tp):
     elements = list(get_all_type_hints(tp).items())
     elements.sort(key=lambda v: v[0])
@@ -39,15 +27,15 @@ def _get_td_hints(tp):
 
 
 def _extract_item_type(tp) -> BaseNormType:
-    if tp.origin is typing.Annotated:
+    if tp.origin is Annotated:
         return tp.args[0]
     return tp
 
 
 def _fetch_required_keys(
-    fields: Sequence[Tuple[str, BaseNormType]],
-    frozen_required_keys: AbstractSet[str],
-) -> Set:
+    fields: Sequence[tuple[str, BaseNormType]],
+    frozen_required_keys: Set[str],
+) -> set:
     required_keys = set(frozen_required_keys)
 
     for field_name, field_tp in fields:
@@ -60,21 +48,10 @@ def _fetch_required_keys(
     return required_keys
 
 
-def _make_requirement_determinant_from_keys(required_fields: set):
+def _make_requirement_determinant(required_fields: set):
     return lambda name: name in required_fields
 
 
-if HAS_PY_39:
-    def _make_requirement_determinant_from_type(tp):
-        required_fields = tp.__required_keys__
-        return lambda name: name in required_fields
-else:
-    def _make_requirement_determinant_from_type(tp):
-        warnings.warn(TypedDictAt38Warning(), stacklevel=3)
-        is_total = tp.__total__
-        return lambda name: is_total
-
-
 def get_typed_dict_shape(tp) -> FullShape:
     # __annotations__ of TypedDict contain also parents' type hints unlike any other classes,
     # so overriden_types always is empty
@@ -90,9 +67,9 @@ def get_typed_dict_shape(tp) -> FullShape:
             [(field_name, field_tp) for (field_name, _), field_tp in zip(type_hints, norm_types)],
             tp.__required_keys__,
         )
-        requirement_determinant = _make_requirement_determinant_from_keys(required_keys)
+        requirement_determinant = _make_requirement_determinant(required_keys)
     else:
-        requirement_determinant = _make_requirement_determinant_from_type(tp)
+        requirement_determinant = _make_requirement_determinant(tp.__required_keys__)
 
     return Shape(
         input=InputShape(
diff --git a/src/adaptix/_internal/morphing/concrete_provider.py b/src/adaptix/_internal/morphing/concrete_provider.py
index 2726a67b..9ea739ce 100644
--- a/src/adaptix/_internal/morphing/concrete_provider.py
+++ b/src/adaptix/_internal/morphing/concrete_provider.py
@@ -2,37 +2,47 @@
 import re
 import typing
 from binascii import a2b_base64, b2a_base64
-from dataclasses import dataclass, replace
-from datetime import date, datetime, time, timedelta
+from dataclasses import replace
+from datetime import date, datetime, time, timedelta, timezone
 from decimal import Decimal, InvalidOperation
 from fractions import Fraction
 from io import BytesIO
-from typing import Generic, Type, TypeVar, Union
+from typing import Generic, Optional, TypeVar, Union
 
 from ..common import Dumper, Loader
 from ..feature_requirement import HAS_PY_311, HAS_SELF_TYPE
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import P, create_loc_stack_checker
-from ..provider.provider_template import for_predicate
-from ..provider.request_cls import LocatedRequest, StrictCoercionRequest, find_owner_with_field
-from ..provider.static_provider import static_provision_action
+from ..provider.loc_stack_tools import find_owner_with_field
+from ..provider.located_request import LocatedRequest, for_predicate
 from ..special_cases_optimization import as_is_stub
+from .json_schema.definitions import JSONSchema
+from .json_schema.request_cls import JSONSchemaRequest
+from .json_schema.schema_model import JSONSchemaBuiltinFormat, JSONSchemaType
 from .load_error import FormatMismatchLoadError, TypeLoadError, ValueLoadError
-from .provider_template import DumperProvider, LoaderProvider, ProviderWithAttachableLSC
-from .request_cls import DumperRequest, LoaderRequest
+from .provider_template import DumperProvider, JSONSchemaProvider, MorphingProvider
+from .request_cls import DumperRequest, LoaderRequest, StrictCoercionRequest
 
-T = TypeVar("T")
 
+class IsoFormatProvider(MorphingProvider):
+    _CLS_TO_JSON_FORMAT = {
+        time: JSONSchemaBuiltinFormat.TIME,
+        date: JSONSchemaBuiltinFormat.DATE,
+        datetime: JSONSchemaBuiltinFormat.DATE_TIME,
+    }
+
+    def __init__(self, cls: type[Union[date, time]]):
+        self._cls = cls
+        self._loc_stack_checker = create_loc_stack_checker(cls)
 
-@dataclass
-class IsoFormatProvider(LoaderProvider, DumperProvider):
-    cls: Type[Union[date, time]]
+    def __repr__(self):
+        return f"{type(self)}(cls={self._cls})"
 
-    def __post_init__(self):
-        self._loc_stack_checker = create_loc_stack_checker(self.cls)
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(self._make_loader)
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        raw_loader = self.cls.fromisoformat
+    def _make_loader(self):
+        raw_loader = self._cls.fromisoformat
 
         def isoformat_loader(data):
             try:
@@ -41,20 +51,31 @@ def isoformat_loader(data):
                 raise TypeLoadError(str, data)
             except ValueError:
                 raise ValueLoadError("Invalid isoformat string", data)
-
         return isoformat_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        return self.cls.isoformat
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
+        return self._cls.isoformat
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.STRING, format=self._CLS_TO_JSON_FORMAT[self._cls])
 
 
-@dataclass
 @for_predicate(datetime)
-class DatetimeFormatProvider(LoaderProvider, DumperProvider):
-    format: str
+class DatetimeFormatProvider(MorphingProvider):
+    def __init__(self, fmt: str):
+        self._fmt = fmt
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        fmt = self.format
+    def __repr__(self):
+        return f"{type(self)}(fmt={self._fmt})"
+
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(self._make_loader)
+
+    def _make_loader(self):
+        fmt = self._fmt
 
         def datetime_format_loader(data):
             try:
@@ -66,20 +87,134 @@ def datetime_format_loader(data):
 
         return datetime_format_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        fmt = self.format
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
+        fmt = self._fmt
 
         def datetime_format_dumper(data: datetime):
             return data.strftime(fmt)
 
         return datetime_format_dumper
 
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.STRING)
+
+
+@for_predicate(datetime)
+class DatetimeTimestampProvider(MorphingProvider):
+    def __init__(self, tz: Optional[timezone]):
+        self._tz = tz
+
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(self._make_loader)
+
+    def _make_loader(self):
+        tz = self._tz
+
+        def datetime_timestamp_loader(data):
+            try:
+                return datetime.fromtimestamp(data, tz=tz)
+            except TypeError:
+                raise TypeLoadError(Union[int, float], data)
+            except ValueError:
+                raise ValueLoadError("Unexpected value", data)
+            except OverflowError:
+                raise ValueLoadError(
+                    "Timestamp is out of the range of supported values",
+                    data,
+                )
+
+        return datetime_timestamp_loader
+
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
+        def datetime_timestamp_dumper(data: datetime):
+            return data.timestamp()
+        return datetime_timestamp_dumper
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.NUMBER)
+
+
+@for_predicate(date)
+class DateTimestampProvider(MorphingProvider):
+    def _is_pydatetime(self) -> bool:
+        try:
+            import _pydatetime
+        except ImportError:
+            return False
+        else:
+            if datetime is _pydatetime:
+                return True
+
+        return False
+
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(self._make_loader)
+
+    def _make_loader(self):
+        def date_timestamp_loader(data):
+            try:
+                # Pure-Python implementation and C-extension implementation
+                # of datetime.date.fromtimestamp module works differently with a None arg.
+                # See https://github.com/python/cpython/issues/120268 for more details.
+                if data is None:
+                    raise TypeLoadError(Union[int, float], data)
+
+                return date.fromtimestamp(data)  # noqa: DTZ012
+            except TypeError:
+                raise TypeLoadError(Union[int, float], data)
+            except ValueError:
+                raise ValueLoadError("Unexpected value", data)
+            except OverflowError:
+                raise ValueLoadError(
+                    "Timestamp is out of the range of supported values",
+                    data,
+                )
+
+        def pydate_timestamp_loader(data):
+            try:
+                return date.fromtimestamp(data)  # noqa: DTZ012
+            except TypeError:
+                raise TypeLoadError(Union[int, float], data)
+            except OverflowError:
+                raise ValueLoadError(
+                    "Timestamp is out of the range of supported values",
+                    data,
+                )
+
+        return pydate_timestamp_loader if self._is_pydatetime() else date_timestamp_loader
+
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
+        def date_timestamp_dumper(data: date):
+            dt = datetime(
+                year=data.year,
+                month=data.month,
+                day=data.day,
+                tzinfo=timezone.utc,
+            )
+            return dt.timestamp()
+        return date_timestamp_dumper
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.NUMBER)
+
 
 @for_predicate(timedelta)
-class SecondsTimedeltaProvider(LoaderProvider, DumperProvider):
+class SecondsTimedeltaProvider(MorphingProvider):
     _OK_TYPES = (int, float, Decimal)
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(self._make_loader)
+
+    def _make_loader(self):
         ok_types = self._OK_TYPES
 
         def timedelta_loader(data):
@@ -89,9 +224,15 @@ def timedelta_loader(data):
 
         return timedelta_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
         return timedelta.total_seconds
 
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.NUMBER)
+
 
 def none_loader(data):
     if data is None:
@@ -100,28 +241,41 @@ def none_loader(data):
 
 
 @for_predicate(None)
-class NoneProvider(LoaderProvider, DumperProvider):
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+class NoneProvider(MorphingProvider):
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         return none_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         return as_is_stub
 
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.NULL)
+
 
-class Base64DumperMixin(DumperProvider):
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+class _Base64DumperMixin(DumperProvider):
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
         def bytes_base64_dumper(data):
             return b2a_base64(data, newline=False).decode("ascii")
-
         return bytes_base64_dumper
 
 
+class _Base64JSONSchemaMixin(JSONSchemaProvider):
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.STRING, content_encoding="base64")
+
+
 B64_PATTERN = re.compile(b"[A-Za-z0-9+/]*={0,2}")
 
 
 @for_predicate(bytes)
-class BytesBase64Provider(LoaderProvider, Base64DumperMixin):
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+class BytesBase64Provider(_Base64DumperMixin, _Base64JSONSchemaMixin, MorphingProvider):
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(self._make_loader)
+
+    def _make_loader(self):
         def bytes_base64_loader(data):
             try:
                 encoded = data.encode("ascii")
@@ -135,52 +289,65 @@ def bytes_base64_loader(data):
                 return a2b_base64(encoded)
             except binascii.Error as e:
                 raise ValueLoadError(str(e), data)
-
         return bytes_base64_loader
 
 
 @for_predicate(BytesIO)
-class BytesIOBase64Provider(BytesBase64Provider):
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        bytes_base64_loader = super()._provide_loader(mediator, request)
+class BytesIOBase64Provider(_Base64JSONSchemaMixin, MorphingProvider):
+    _BYTES_PROVIDER = BytesBase64Provider()
 
-        def bytes_io_base64_loader(data):
-            return BytesIO(bytes_base64_loader(data))
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(
+            self._make_loader,
+            loader=self._BYTES_PROVIDER.provide_loader(mediator, request),
+        )
 
+    def _make_loader(self, loader: Loader):
+        def bytes_io_base64_loader(data):
+            return BytesIO(loader(data))
         return bytes_io_base64_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
         def bytes_io_base64_dumper(data: BytesIO):
             return b2a_base64(data.getvalue(), newline=False).decode("ascii")
-
         return bytes_io_base64_dumper
 
 
 @for_predicate(typing.IO[bytes])
-class IOBytesBase64Provider(BytesIOBase64Provider):
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+class IOBytesBase64Provider(BytesIOBase64Provider, _Base64JSONSchemaMixin, MorphingProvider):
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(self._make_dumper)
+
+    def _make_dumper(self):
         def io_bytes_base64_dumper(data: typing.IO[bytes]):
             if data.seekable():
                 data.seek(0)
 
             return b2a_base64(data.read(), newline=False).decode("ascii")
-
         return io_bytes_base64_dumper
 
 
 @for_predicate(bytearray)
-class BytearrayBase64Provider(LoaderProvider, Base64DumperMixin):
+class BytearrayBase64Provider(_Base64DumperMixin, _Base64JSONSchemaMixin, MorphingProvider):
     _BYTES_PROVIDER = BytesBase64Provider()
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        bytes_loader = self._BYTES_PROVIDER.apply_provider(
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        bytes_loader = self._BYTES_PROVIDER.provide_loader(
             mediator,
             replace(request, loc_stack=request.loc_stack.replace_last_type(bytes)),
         )
 
-        def bytearray_base64_loader(data):
-            return bytearray(bytes_loader(data))
+        return mediator.cached_call(
+            self._make_loader,
+            loader=bytes_loader,
+        )
 
+    def _make_loader(self, loader: Loader):
+        def bytearray_base64_loader(data):
+            return bytearray(loader(data))
         return bytearray_base64_loader
 
 
@@ -189,11 +356,14 @@ def _regex_dumper(data: re.Pattern):
 
 
 @for_predicate(re.Pattern)
-class RegexPatternProvider(LoaderProvider, DumperProvider):
+class RegexPatternProvider(MorphingProvider):
     def __init__(self, flags: re.RegexFlag = re.RegexFlag(0)):
         self.flags = flags
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(self._make_loader)
+
+    def _make_loader(self):
         flags = self.flags
         re_compile = re.compile
 
@@ -208,24 +378,51 @@ def regex_loader(data):
 
         return regex_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         return _regex_dumper
 
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.STRING, format=JSONSchemaBuiltinFormat.REGEX)
+
+
+T = TypeVar("T")
+
 
-class ScalarLoaderProvider(LoaderProvider, Generic[T]):
-    def __init__(self, pred: Type[T], strict_coercion_loader: Loader[T], lax_coercion_loader: Loader[T]):
-        self._loc_stack_checker = create_loc_stack_checker(pred)
-        self._pred = pred
+class ScalarProvider(MorphingProvider, Generic[T]):
+    def __init__(
+        self,
+        target: type[T],
+        strict_coercion_loader: Loader[T],
+        lax_coercion_loader: Loader[T],
+        dumper: Dumper[T],
+        json_schema: JSONSchema,
+    ):
+        self._target = target
+        self._loc_stack_checker = create_loc_stack_checker(target)
         self._strict_coercion_loader = strict_coercion_loader
         self._lax_coercion_loader = lax_coercion_loader
+        self._dumper = dumper
+        self._json_schema = json_schema
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+        return mediator.cached_call(
+            self._make_loader,
+            strict_coercion=strict_coercion,
+        )
+
+    def _make_loader(self, *, strict_coercion: bool):
         return self._strict_coercion_loader if strict_coercion else self._lax_coercion_loader
 
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return self._dumper
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return self._json_schema
+
 
 def int_strict_coercion_loader(data):
-    if type(data) is int:  # noqa: E721
+    if type(data) is int:
         return data
     raise TypeLoadError(int, data)
 
@@ -242,10 +439,12 @@ def int_lax_coercion_loader(data):
         raise TypeLoadError(Union[int, float, str], data)
 
 
-INT_LOADER_PROVIDER = ScalarLoaderProvider(
-    pred=int,
+INT_PROVIDER = ScalarProvider(
+    target=int,
     strict_coercion_loader=int_strict_coercion_loader,
     lax_coercion_loader=int_lax_coercion_loader,
+    dumper=as_is_stub,
+    json_schema=JSONSchema(type=JSONSchemaType.INTEGER),
 )
 
 
@@ -267,41 +466,47 @@ def float_lax_coercion_loader(data):
         raise TypeLoadError(Union[int, float, str], data)
 
 
-FLOAT_LOADER_PROVIDER = ScalarLoaderProvider(
-    pred=float,
+FLOAT_PROVIDER = ScalarProvider(
+    target=float,
     strict_coercion_loader=float_strict_coercion_loader,
     lax_coercion_loader=float_lax_coercion_loader,
+    dumper=as_is_stub,
+    json_schema=JSONSchema(type=JSONSchemaType.NUMBER),
 )
 
 
 def str_strict_coercion_loader(data):
-    if type(data) is str:  # noqa: E721
+    if type(data) is str:
         return data
     raise TypeLoadError(str, data)
 
 
-STR_LOADER_PROVIDER = ScalarLoaderProvider(
-    pred=str,
+STR_PROVIDER = ScalarProvider(
+    target=str,
     strict_coercion_loader=str_strict_coercion_loader,
     lax_coercion_loader=str,
+    dumper=as_is_stub,
+    json_schema=JSONSchema(type=JSONSchemaType.INTEGER),
 )
 
 
 def bool_strict_coercion_loader(data):
-    if type(data) is bool:  # noqa: E721
+    if type(data) is bool:
         return data
     raise TypeLoadError(bool, data)
 
 
-BOOL_LOADER_PROVIDER = ScalarLoaderProvider(
-    pred=bool,
+BOOL_PROVIDER = ScalarProvider(
+    target=bool,
     strict_coercion_loader=bool_strict_coercion_loader,
     lax_coercion_loader=bool,
+    dumper=as_is_stub,
+    json_schema=JSONSchema(type=JSONSchemaType.BOOLEAN),
 )
 
 
 def decimal_strict_coercion_loader(data):
-    if type(data) is str:  # noqa: E721
+    if type(data) is str:
         try:
             return Decimal(data)
         except InvalidOperation:
@@ -322,10 +527,12 @@ def decimal_lax_coercion_loader(data):
         raise ValueLoadError(str(e), data)
 
 
-DECIMAL_LOADER_PROVIDER = ScalarLoaderProvider(
-    pred=Decimal,
+DECIMAL_PROVIDER = ScalarProvider(
+    target=Decimal,
     strict_coercion_loader=decimal_strict_coercion_loader,
     lax_coercion_loader=decimal_lax_coercion_loader,
+    dumper=Decimal.__str__,
+    json_schema=JSONSchema(type=JSONSchemaType.STRING),
 )
 
 
@@ -350,10 +557,12 @@ def fraction_lax_coercion_loader(data):
         raise ValueLoadError(str(e), data)
 
 
-FRACTION_LOADER_PROVIDER = ScalarLoaderProvider(
-    pred=Fraction,
+FRACTION_PROVIDER = ScalarProvider(
+    target=Fraction,
     strict_coercion_loader=fraction_strict_coercion_loader,
     lax_coercion_loader=fraction_lax_coercion_loader,
+    dumper=Fraction.__str__,
+    json_schema=JSONSchema(type=JSONSchemaType.STRING),
 )
 
 
@@ -375,19 +584,18 @@ def complex_lax_coercion_loader(data):
         raise ValueLoadError("Bad string format", data)
 
 
-COMPLEX_LOADER_PROVIDER = ScalarLoaderProvider(
-    pred=complex,
+COMPLEX_PROVIDER = ScalarProvider(
+    target=complex,
     strict_coercion_loader=complex_strict_coercion_loader,
     lax_coercion_loader=complex_lax_coercion_loader,
+    dumper=complex.__str__,
+    json_schema=JSONSchema(type=JSONSchemaType.STRING),
 )
 
 
 @for_predicate(typing.Self if HAS_SELF_TYPE else ~P.ANY)
-class SelfTypeProvider(ProviderWithAttachableLSC):
-    @static_provision_action
-    def _provide_substitute(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        self._apply_loc_stack_checker(mediator, request)
-
+class SelfTypeProvider(MorphingProvider):
+    def _substituting_provide(self, mediator: Mediator, request: LocatedRequest):
         try:
             owner_loc, _field_loc = find_owner_with_field(request.loc_stack)
         except ValueError:
@@ -404,12 +612,24 @@ def _provide_substitute(self, mediator: Mediator, request: LocatedRequest) -> Lo
             ),
         )
 
+    def provide_loader(self, mediator: Mediator[Loader], request: LoaderRequest) -> Loader:
+        return self._substituting_provide(mediator, request)
+
+    def provide_dumper(self, mediator: Mediator[Dumper], request: DumperRequest) -> Dumper:
+        return self._substituting_provide(mediator, request)
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return self._substituting_provide(mediator, request)
+
 
 @for_predicate(typing.LiteralString if HAS_PY_311 else ~P.ANY)
-class LiteralStringProvider(LoaderProvider, DumperProvider):
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+class LiteralStringProvider(MorphingProvider):
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
         return str_strict_coercion_loader if strict_coercion else str  # type: ignore[return-value]
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         return as_is_stub
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        return JSONSchema(type=JSONSchemaType.STRING)
diff --git a/src/adaptix/_internal/morphing/constant_length_tuple_provider.py b/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
index 3f9bc188..27a26afe 100644
--- a/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
+++ b/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
@@ -1,16 +1,15 @@
 # ruff: noqa: SIM113
 import collections.abc
 import typing
-from typing import Collection, Mapping, Tuple
+from collections.abc import Collection, Mapping
 
 from ..common import Dumper, Loader
 from ..compat import CompatExceptionGroup
 from ..definitions import DebugTrail
 from ..feature_requirement import HAS_UNPACK
 from ..provider.essential import CannotProvide, Mediator
+from ..provider.located_request import for_predicate
 from ..provider.location import GenericParamLoc
-from ..provider.provider_template import for_predicate
-from ..provider.request_cls import DebugTrailRequest, StrictCoercionRequest, get_type_from_request, try_normalize_type
 from ..struct_trail import append_trail, render_trail_as_note
 from .load_error import (
     AggregateLoadError,
@@ -21,15 +20,16 @@
     TypeLoadError,
 )
 from .provider_template import DumperProvider, LoaderProvider
-from .request_cls import DumperRequest, LoaderRequest
+from .request_cls import DebugTrailRequest, DumperRequest, LoaderRequest, StrictCoercionRequest
+from .utils import try_normalize_type
 
 CollectionsMapping = collections.abc.Mapping
 
 
-@for_predicate(Tuple)
+@for_predicate(tuple)
 class ConstantLengthTupleProvider(LoaderProvider, DumperProvider):
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        norm = try_normalize_type(get_type_from_request(request))
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        norm = try_normalize_type(request.last_loc.type)
         if len(norm.args) > 1 and norm.args[1] == Ellipsis:
             raise CannotProvide
         if HAS_UNPACK and any(arg.origin == typing.Unpack for arg in norm.args if arg != Ellipsis):
@@ -41,21 +41,19 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
 
         loaders = mediator.mandatory_provide_by_iterable(
             [
-                LoaderRequest(
-                    loc_stack=request.loc_stack.append_with(
-                        GenericParamLoc(
-                            type=tp.source,
-                            generic_pos=i,
-                        ),
-                    ),
-                )
+                request.append_loc(GenericParamLoc(type=tp.source, generic_pos=i))
                 for i, tp in enumerate(norm.args)
             ],
             lambda: "Cannot create loader for tuple. Loaders for some elements cannot be created",
         )
         strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
         debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
-        return self._make_loader(tuple(loaders), strict_coercion=strict_coercion, debug_trail=debug_trail)
+        return mediator.cached_call(
+            self._make_loader,
+            loaders=tuple(loaders),
+            strict_coercion=strict_coercion,
+            debug_trail=debug_trail,
+        )
 
     def _make_loader(self, loaders: Collection[Loader], *, strict_coercion: bool, debug_trail: DebugTrail):
         if debug_trail == DebugTrail.DISABLE:
@@ -86,7 +84,7 @@ def _get_dt_sc_loader(self, tuple_mapper):
         def dt_sc_loader(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(tuple, Mapping, data)
-            if type(data) is str:  # noqa: E721
+            if type(data) is str:
                 raise ExcludedTypeLoadError(tuple, str, data)
 
             try:
@@ -193,7 +191,7 @@ def _get_dt_disable_sc_loader(self, loaders: Collection[Loader]):
         def dt_disable_sc_loader(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(tuple, Mapping, data)
-            if type(data) is str:  # noqa: E721
+            if type(data) is str:
                 raise ExcludedTypeLoadError(tuple, str, data)
 
             try:
@@ -214,8 +212,8 @@ def dt_disable_sc_loader(data):
 
         return dt_disable_sc_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        norm = try_normalize_type(get_type_from_request(request))
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        norm = try_normalize_type(request.last_loc.type)
         if len(norm.args) > 1 and norm.args[1] == Ellipsis:
             raise CannotProvide
         if HAS_UNPACK and any(arg.origin == typing.Unpack for arg in norm.args if arg != Ellipsis):
@@ -226,20 +224,17 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
             )
         dumpers = mediator.mandatory_provide_by_iterable(
             [
-                DumperRequest(
-                    loc_stack=request.loc_stack.append_with(
-                        GenericParamLoc(
-                            type=tp.source,
-                            generic_pos=i,
-                        ),
-                    ),
-                )
+                request.append_loc(GenericParamLoc(type=tp.source, generic_pos=i))
                 for i, tp in enumerate(norm.args)
             ],
             lambda: "Cannot create dumper for tuple. Dumpers for some elements cannot be created",
         )
         debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
-        return self._make_dumper(tuple(dumpers), debug_trail)
+        return mediator.cached_call(
+            self._make_dumper,
+            dumpers=tuple(dumpers),
+            debug_trail=debug_trail,
+        )
 
     def _make_dumper(self, dumpers: Collection[Dumper], debug_trail: DebugTrail):
         if debug_trail == DebugTrail.DISABLE:
diff --git a/src/adaptix/_internal/morphing/dict_provider.py b/src/adaptix/_internal/morphing/dict_provider.py
index 5036a033..83baea9b 100644
--- a/src/adaptix/_internal/morphing/dict_provider.py
+++ b/src/adaptix/_internal/morphing/dict_provider.py
@@ -1,51 +1,43 @@
 import collections.abc
 from collections import defaultdict
+from collections.abc import Mapping
 from dataclasses import replace
-from typing import Callable, DefaultDict, Dict, Mapping, Optional, Tuple
+from typing import Callable, Optional
 
 from ..common import Dumper, Loader
 from ..compat import CompatExceptionGroup
 from ..definitions import DebugTrail
 from ..morphing.provider_template import DumperProvider, LoaderProvider
 from ..provider.essential import Mediator
+from ..provider.located_request import LocatedRequest, for_predicate
 from ..provider.location import GenericParamLoc
-from ..provider.provider_template import for_predicate
-from ..provider.request_cls import DebugTrailRequest, LocatedRequest, get_type_from_request, try_normalize_type
 from ..struct_trail import ItemKey, append_trail, render_trail_as_note
 from ..type_tools import BaseNormType
 from .load_error import AggregateLoadError, LoadError, TypeLoadError
-from .request_cls import DumperRequest, LoaderRequest
+from .request_cls import DebugTrailRequest, DumperRequest, LoaderRequest
+from .utils import try_normalize_type
 
 CollectionsMapping = collections.abc.Mapping
 
 
-@for_predicate(Dict)
+@for_predicate(dict)
 class DictProvider(LoaderProvider, DumperProvider):
-    def _extract_key_value(self, request: LocatedRequest) -> Tuple[BaseNormType, BaseNormType]:
-        norm = try_normalize_type(get_type_from_request(request))
+    def _extract_key_value(self, request: LocatedRequest) -> tuple[BaseNormType, BaseNormType]:
+        norm = try_normalize_type(request.last_loc.type)
         return norm.args
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         key, value = self._extract_key_value(request)
 
         key_loader = mediator.mandatory_provide(
-            LoaderRequest(
-                loc_stack=request.loc_stack.append_with(
-                    GenericParamLoc(
-                        type=key.source,
-                        generic_pos=0,
-                    ),
-                ),
-            ),
+            request.append_loc(GenericParamLoc(type=key.source, generic_pos=0)),
             lambda x: "Cannot create loader for dict. Loader for key cannot be created",
         )
         value_loader = mediator.mandatory_provide(
-            LoaderRequest(
-                loc_stack=request.loc_stack.append_with(
-                    GenericParamLoc(
-                        type=value.source,
-                        generic_pos=1,
-                    ),
+            request.append_loc(
+                GenericParamLoc(
+                    type=value.source,
+                    generic_pos=1,
                 ),
             ),
             lambda x: "Cannot create loader for dict. Loader for value cannot be created",
@@ -53,7 +45,8 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         debug_trail = mediator.mandatory_provide(
             DebugTrailRequest(loc_stack=request.loc_stack),
         )
-        return self._make_loader(
+        return mediator.cached_call(
+            self._make_loader,
             key_loader=key_loader,
             value_loader=value_loader,
             debug_trail=debug_trail,
@@ -154,35 +147,22 @@ def dict_loader_dt_all(data):
 
         return dict_loader_dt_all
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         key, value = self._extract_key_value(request)
 
         key_dumper = mediator.mandatory_provide(
-            DumperRequest(
-                loc_stack=request.loc_stack.append_with(
-                    GenericParamLoc(
-                        type=key.source,
-                        generic_pos=0,
-                    ),
-                ),
-            ),
+            request.append_loc(GenericParamLoc(type=key.source, generic_pos=0)),
             lambda x: "Cannot create dumper for dict. Dumper for key cannot be created",
         )
         value_dumper = mediator.mandatory_provide(
-            DumperRequest(
-                loc_stack=request.loc_stack.append_with(
-                    GenericParamLoc(
-                        type=value.source,
-                        generic_pos=1,
-                    ),
-                ),
-            ),
+            request.append_loc(GenericParamLoc(type=value.source, generic_pos=1)),
             lambda x: "Cannot create dumper for dict. Dumper for value cannot be created",
         )
         debug_trail = mediator.mandatory_provide(
             DebugTrailRequest(loc_stack=request.loc_stack),
         )
-        return self._make_dumper(
+        return mediator.cached_call(
+            self._make_dumper,
             key_dumper=key_dumper,
             value_dumper=value_dumper,
             debug_trail=debug_trail,
@@ -266,36 +246,42 @@ def dict_dumper_dt_all(data: Mapping):
         return dict_dumper_dt_all
 
 
-@for_predicate(DefaultDict)
+@for_predicate(defaultdict)
 class DefaultDictProvider(LoaderProvider, DumperProvider):
     _DICT_PROVIDER = DictProvider()
 
     def __init__(self, default_factory: Optional[Callable] = None):
         self.default_factory = default_factory
 
-    def _extract_key_value(self, request: LocatedRequest) -> Tuple[BaseNormType, BaseNormType]:
-        norm = try_normalize_type(get_type_from_request(request))
+    def _extract_key_value(self, request: LocatedRequest) -> tuple[BaseNormType, BaseNormType]:
+        norm = try_normalize_type(request.last_loc.type)
         return norm.args
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         key, value = self._extract_key_value(request)
-        dict_type_hint = Dict[key.source, value.source]  # type: ignore[misc, name-defined]
-        dict_loader = self._DICT_PROVIDER.apply_provider(
+        dict_type_hint = dict[key.source, value.source]  # type: ignore[misc, name-defined]
+        dict_loader = self._DICT_PROVIDER.provide_loader(
             mediator,
             replace(request, loc_stack=request.loc_stack.replace_last_type(dict_type_hint)),
         )
+
+        return mediator.cached_call(
+            self._make_loader,
+            loader=dict_loader,
+        )
+
+    def _make_loader(self, loader: Loader):
         default_factory = self.default_factory
 
         def defaultdict_loader(data):
-            return defaultdict(default_factory, dict_loader(data))
+            return defaultdict(default_factory, loader(data))
 
         return defaultdict_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         key, value = self._extract_key_value(request)
-        dict_type_hint = Dict[key.source, value.source]  # type: ignore[misc, name-defined]
-
-        return self._DICT_PROVIDER.apply_provider(
+        dict_type_hint = dict[key.source, value.source]  # type: ignore[misc, name-defined]
+        return self._DICT_PROVIDER.provide_dumper(
             mediator,
-            replace(request, loc_stack=request.loc_stack.replace_last_type(dict_type_hint)),
+            request=replace(request, loc_stack=request.loc_stack.replace_last_type(dict_type_hint)),
         )
diff --git a/src/adaptix/_internal/morphing/enum_provider.py b/src/adaptix/_internal/morphing/enum_provider.py
index 5fd39a30..86c22b94 100644
--- a/src/adaptix/_internal/morphing/enum_provider.py
+++ b/src/adaptix/_internal/morphing/enum_provider.py
@@ -1,18 +1,19 @@
 import collections
 import math
 from abc import ABC, abstractmethod
+from collections.abc import Iterable, Mapping, Sequence
 from enum import Enum, EnumMeta, Flag
 from functools import reduce
 from operator import or_
-from typing import Any, Iterable, Mapping, Optional, Sequence, Type, TypeVar, Union, final
+from typing import Any, Optional, TypeVar, Union, final
 
 from ..common import Dumper, Loader, TypeHint
 from ..morphing.provider_template import DumperProvider, LoaderProvider
 from ..name_style import NameStyle, convert_snake_style
 from ..provider.essential import CannotProvide, Mediator
-from ..provider.loc_stack_filtering import DirectMediator, LastLocMapChecker
-from ..provider.provider_template import for_predicate
-from ..provider.request_cls import StrictCoercionRequest, TypeHintLoc, get_type_from_request
+from ..provider.loc_stack_filtering import DirectMediator, LastLocChecker
+from ..provider.located_request import for_predicate
+from ..provider.location import TypeHintLoc
 from ..type_tools import is_subclass_soft, normalize_type
 from .load_error import (
     BadVariantLoadError,
@@ -23,7 +24,7 @@
     OutOfRangeLoadError,
     TypeLoadError,
 )
-from .request_cls import DumperRequest, LoaderRequest
+from .request_cls import DumperRequest, LoaderRequest, StrictCoercionRequest
 
 EnumT = TypeVar("EnumT", bound=Enum)
 FlagT = TypeVar("FlagT", bound=Flag)
@@ -73,7 +74,7 @@ def _generate_mapping(self, cases: Iterable[EnumT]) -> Mapping[EnumT, str]:
         return result
 
 
-class AnyEnumLSC(LastLocMapChecker):
+class AnyEnumLSC(LastLocChecker):
     def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
         try:
             norm = normalize_type(loc.type)
@@ -83,7 +84,7 @@ def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
         return isinstance(origin, EnumMeta) and not is_subclass_soft(origin, Flag)
 
 
-class FlagEnumLSC(LastLocMapChecker):
+class FlagEnumLSC(LastLocChecker):
     def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
         try:
             norm = normalize_type(loc.type)
@@ -107,8 +108,13 @@ class EnumNameProvider(BaseEnumProvider):
     def __init__(self, mapping_generator: BaseEnumMappingGenerator):
         self._mapping_generator = mapping_generator
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        enum = get_type_from_request(request)
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(
+            self._make_loader,
+            enum=request.last_loc.type,
+        )
+
+    def _make_loader(self, enum):
         mapping = self._mapping_generator.generate_for_loading(enum.__members__.values())
         variants = list(mapping.keys())
 
@@ -122,8 +128,15 @@ def enum_loader(data):
 
         return enum_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        enum = get_type_from_request(request)
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        enum = request.last_loc.type
+
+        return mediator.cached_call(
+            self._make_dumper,
+            enum=enum,
+        )
+
+    def _make_dumper(self, enum):
         mapping = self._mapping_generator.generate_for_dumping(enum.__members__.values())
 
         def enum_dumper(data: Enum) -> str:
@@ -136,16 +149,19 @@ class EnumValueProvider(BaseEnumProvider):
     def __init__(self, value_type: TypeHint):
         self._value_type = value_type
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        enum = get_type_from_request(request)
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        enum = request.last_loc.type
         value_loader = mediator.mandatory_provide(
-            LoaderRequest(
-                loc_stack=request.loc_stack.append_with(
-                    TypeHintLoc(type=self._value_type),
-                ),
-            ),
+            request.append_loc(TypeHintLoc(type=self._value_type)),
         )
 
+        return mediator.cached_call(
+            self._make_loader,
+            enum=enum,
+            value_loader=value_loader,
+        )
+
+    def _make_loader(self, enum: Enum, value_loader: Loader):
         def enum_loader(data):
             loaded_value = value_loader(data)
             try:
@@ -155,15 +171,17 @@ def enum_loader(data):
 
         return enum_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         value_dumper = mediator.mandatory_provide(
-            DumperRequest(
-                loc_stack=request.loc_stack.append_with(
-                    TypeHintLoc(type=self._value_type),
-                ),
-            ),
+            request.append_loc(TypeHintLoc(type=self._value_type)),
+        )
+
+        return mediator.cached_call(
+            self._make_dumper,
+            value_dumper=value_dumper,
         )
 
+    def _make_dumper(self, value_dumper: Dumper):
         def enum_dumper(data):
             return value_dumper(data.value)
 
@@ -175,12 +193,14 @@ class EnumExactValueProvider(BaseEnumProvider):
     by their value without any processing
     """
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        return self._make_loader(get_type_from_request(request))
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(
+            self._make_loader,
+            enum=request.last_loc.type,
+        )
 
     def _make_loader(self, enum):
         variants = [case.value for case in enum]
-
         value_to_member = self._get_exact_value_to_member(enum)
         if value_to_member is None:
             def enum_exact_loader(data):
@@ -205,7 +225,7 @@ def enum_exact_loader_v2m(data):
 
         return enum_exact_loader_v2m
 
-    def _get_exact_value_to_member(self, enum: Type[Enum]) -> Optional[Mapping[Any, Any]]:
+    def _get_exact_value_to_member(self, enum: type[Enum]) -> Optional[Mapping[Any, Any]]:
         try:
             value_to_member = {member.value: member for member in enum}
         except TypeError:
@@ -216,8 +236,14 @@ def _get_exact_value_to_member(self, enum: Type[Enum]) -> Optional[Mapping[Any,
 
         return value_to_member
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        member_to_value = {member: member.value for member in get_type_from_request(request)}
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(
+            self._make_dumper,
+            enum=request.last_loc.type,
+        )
+
+    def _make_dumper(self, enum):
+        member_to_value = {member: member.value for member in enum}
 
         def enum_exact_value_dumper(data):
             return member_to_value[data]
@@ -226,8 +252,13 @@ def enum_exact_value_dumper(data):
 
 
 class FlagByExactValueProvider(BaseFlagProvider):
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        enum = get_type_from_request(request)
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        return mediator.cached_call(
+            self._make_loader,
+            enum=request.last_loc.type,
+        )
+
+    def _make_loader(self, enum):
         flag_mask = reduce(or_, enum.__members__.values()).value
 
         if flag_mask < 0:
@@ -246,7 +277,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             )
 
         def flag_loader(data):
-            if type(data) is not int:  # noqa: E721
+            if type(data) is not int:
                 raise TypeLoadError(int, data)
 
             if data < 0 or data > flag_mask:
@@ -258,14 +289,15 @@ def flag_loader(data):
 
         return flag_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        def flag_exact_value_dumper(data):
-            return data.value
-
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         return flag_exact_value_dumper
 
 
-def _extract_non_compound_cases_from_flag(enum: Type[FlagT]) -> Sequence[FlagT]:
+def flag_exact_value_dumper(data):
+    return data.value
+
+
+def _extract_non_compound_cases_from_flag(enum: type[FlagT]) -> Sequence[FlagT]:
     return [case for case in enum.__members__.values() if not math.log2(case.value) % 1]
 
 
@@ -283,15 +315,22 @@ def __init__(
         self._allow_duplicates = allow_duplicates
         self._allow_compound = allow_compound
 
-    def _get_cases(self, enum: Type[FlagT]) -> Sequence[FlagT]:
+    def _get_cases(self, enum: type[FlagT]) -> Sequence[FlagT]:
         if self._allow_compound:
             return list(enum.__members__.values())
         return _extract_non_compound_cases_from_flag(enum)
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        enum = get_type_from_request(request)
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        enum = request.last_loc.type
 
         strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+        return mediator.cached_call(
+            self._make_loader,
+            enum=enum,
+            strict_coercion=strict_coercion,
+        )
+
+    def _make_loader(self, enum, *, strict_coercion: bool):
         allow_single_value = self._allow_single_value
         allow_duplicates = self._allow_duplicates
 
@@ -338,9 +377,13 @@ def flag_loader(data) -> Flag:
 
         return flag_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        enum = get_type_from_request(request)
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return mediator.cached_call(
+            self._make_dumper,
+            enum=request.last_loc.type,
+        )
 
+    def _make_dumper(self, enum):
         cases = self._get_cases(enum)
         need_to_reverse = self._allow_compound and cases != _extract_non_compound_cases_from_flag(enum)
         if need_to_reverse:
diff --git a/src/adaptix/_internal/morphing/facade/func.py b/src/adaptix/_internal/morphing/facade/func.py
index c01366a0..5281a3c3 100644
--- a/src/adaptix/_internal/morphing/facade/func.py
+++ b/src/adaptix/_internal/morphing/facade/func.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, Type, TypeVar, overload
+from typing import Any, Optional, TypeVar, overload
 
 from ...common import TypeHint
 from .retort import Retort
@@ -8,7 +8,7 @@
 
 
 @overload
-def load(data: Any, tp: Type[T], /) -> T:
+def load(data: Any, tp: type[T], /) -> T:
     ...
 
 
@@ -22,7 +22,7 @@ def load(data: Any, tp: TypeHint, /):
 
 
 @overload
-def dump(data: T, tp: Type[T], /) -> Any:
+def dump(data: T, tp: type[T], /) -> Any:
     ...
 
 
diff --git a/src/adaptix/_internal/morphing/facade/provider.py b/src/adaptix/_internal/morphing/facade/provider.py
index 2da3eb03..e68da89d 100644
--- a/src/adaptix/_internal/morphing/facade/provider.py
+++ b/src/adaptix/_internal/morphing/facade/provider.py
@@ -1,8 +1,10 @@
 from __future__ import annotations
 
+from collections.abc import Iterable, Mapping
+from datetime import timezone
 from enum import Enum, EnumMeta
 from types import MappingProxyType
-from typing import Any, Callable, Iterable, List, Mapping, Optional, TypeVar, Union
+from typing import Any, Callable, Optional, TypeVar, Union
 
 from ...common import Catchable, Dumper, Loader, TypeHint, VarTuple
 from ...model_tools.definitions import Default, DescriptorAccessor, NoDefault, OutputField
@@ -15,15 +17,17 @@
     LocStackChecker,
     LocStackPattern,
     OrLocStackChecker,
+    P,
     Pred,
     create_loc_stack_checker,
 )
 from ...provider.overlay_schema import OverlayProvider
-from ...provider.provider_template import ValueProvider
 from ...provider.provider_wrapper import Chain, ChainingProvider
 from ...provider.shape_provider import PropertyExtender
+from ...provider.value_provider import ValueProvider
 from ...special_cases_optimization import as_is_stub
 from ...utils import Omittable, Omitted
+from ..concrete_provider import DatetimeFormatProvider, DateTimestampProvider, DatetimeTimestampProvider
 from ..dict_provider import DefaultDictProvider
 from ..enum_provider import (
     ByNameEnumMappingGenerator,
@@ -140,7 +144,7 @@ def _name_mapping_convert_map(name_map: Omittable[NameMap]) -> VarTuple[Provider
         return (
             DictNameMappingProvider(name_map),
         )
-    result: List[Provider] = []
+    result: list[Provider] = []
     for element in name_map:
         if isinstance(element, Provider):
             result.append(element)
@@ -151,9 +155,9 @@ def _name_mapping_convert_map(name_map: Omittable[NameMap]) -> VarTuple[Provider
         else:
             pred, value = element
             result.append(
-                FuncNameMappingProvider(create_loc_stack_checker(pred), value)
+                bound(pred, FuncNameMappingProvider(value))
                 if callable(value) else
-                ConstNameMappingProvider(create_loc_stack_checker(pred), value),
+                bound(pred, ConstNameMappingProvider(value)),
             )
     return tuple(result)
 
@@ -439,3 +443,35 @@ def default_dict(pred: Pred, default_factory: Callable) -> Provider:
     :param default_factory: default_factory parameter of the ``defaultdict`` instance to be created by the loader
     """
     return bound(pred, DefaultDictProvider(default_factory))
+
+
+def datetime_by_timestamp(pred: Pred = P.ANY, *, tz: Optional[timezone] = timezone.utc) -> Provider:
+    """Provider that can load/dump datetime object from/to UNIX timestamp.
+
+    :param pred: Predicate specifying where the provider should be used.
+        See :ref:`predicate-system` for details.
+    :param tz: tz parameter which will be passed to the datetime.fromtimestamp method.
+    """
+
+    return bound(pred, DatetimeTimestampProvider(tz))
+
+
+def datetime_by_format(pred: Pred = P.ANY, *, fmt: str) -> Provider:
+    """Provider that can load/dump datetime object from/to format string e.g "%d/%m/%y %H:%M"
+
+    :param pred: Predicate specifying where the provider should be used.
+        See :ref:`predicate-system` for details.
+    :param fmt: format parameter which will be passed to datetime.strptime method.
+    """
+    return bound(pred, DatetimeFormatProvider(fmt))
+
+
+def date_by_timestamp(pred: Pred = P.ANY) -> Provider:
+    """Provider that can load date object from UNIX timestamp.
+    Note that date objects can`t be dumped to the UNIX timestamp
+
+    :param pred: Predicate specifying where the provider should be used.
+        See :ref:`predicate-system` for details.
+    """
+    return bound(pred, DateTimestampProvider())
+
diff --git a/src/adaptix/_internal/morphing/facade/retort.py b/src/adaptix/_internal/morphing/facade/retort.py
index f4983613..75fad896 100644
--- a/src/adaptix/_internal/morphing/facade/retort.py
+++ b/src/adaptix/_internal/morphing/facade/retort.py
@@ -1,31 +1,30 @@
 from abc import ABC
+from collections.abc import ByteString, Iterable, Mapping, MutableMapping  # noqa: PYI057
 from datetime import date, datetime, time
-from decimal import Decimal
-from fractions import Fraction
 from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network
 from itertools import chain
 from pathlib import Path, PosixPath, PurePath, PurePosixPath, PureWindowsPath, WindowsPath
-from typing import Any, ByteString, Iterable, Mapping, MutableMapping, Optional, Type, TypeVar, overload
+from typing import Any, Optional, TypeVar, overload
 from uuid import UUID
 
 from ...common import Dumper, Loader, TypeHint, VarTuple
 from ...definitions import DebugTrail
 from ...provider.essential import Provider, Request
-from ...provider.loc_stack_filtering import P
-from ...provider.provider_template import ValueProvider
-from ...provider.request_cls import DebugTrailRequest, LocStack, StrictCoercionRequest, TypeHintLoc
+from ...provider.loc_stack_filtering import LocStack, P
+from ...provider.location import TypeHintLoc
 from ...provider.shape_provider import BUILTIN_SHAPE_PROVIDER
+from ...provider.value_provider import ValueProvider
 from ...retort.operating_retort import OperatingRetort
 from ...struct_trail import render_trail_as_note
 from ...type_tools.basic_utils import is_generic_class
 from ..concrete_provider import (
-    BOOL_LOADER_PROVIDER,
-    COMPLEX_LOADER_PROVIDER,
-    DECIMAL_LOADER_PROVIDER,
-    FLOAT_LOADER_PROVIDER,
-    FRACTION_LOADER_PROVIDER,
-    INT_LOADER_PROVIDER,
-    STR_LOADER_PROVIDER,
+    BOOL_PROVIDER,
+    COMPLEX_PROVIDER,
+    DECIMAL_PROVIDER,
+    FLOAT_PROVIDER,
+    FRACTION_PROVIDER,
+    INT_PROVIDER,
+    STR_PROVIDER,
     BytearrayBase64Provider,
     BytesBase64Provider,
     BytesIOBase64Provider,
@@ -55,7 +54,7 @@
 from ..name_layout.name_mapping import SkipPrivateFieldsNameMappingProvider
 from ..name_layout.provider import BuiltinNameLayoutProvider
 from ..provider_template import ABCProxy
-from ..request_cls import DumperRequest, LoaderRequest
+from ..request_cls import DebugTrailRequest, DumperRequest, LoaderRequest, StrictCoercionRequest
 from .provider import as_is_dumper, as_is_loader, dumper, enum_by_exact_value, flag_by_exact_value, loader, name_mapping
 
 
@@ -78,26 +77,13 @@ class FilledRetort(OperatingRetort, ABC):
         flag_by_exact_value(),
         enum_by_exact_value(),  # it has higher priority than scalar types for Enum with mixins
 
-        INT_LOADER_PROVIDER,
-        as_is_dumper(int),
-
-        FLOAT_LOADER_PROVIDER,
-        as_is_dumper(float),
-
-        STR_LOADER_PROVIDER,
-        as_is_dumper(str),
-
-        BOOL_LOADER_PROVIDER,
-        as_is_dumper(bool),
-
-        DECIMAL_LOADER_PROVIDER,
-        dumper(Decimal, Decimal.__str__),
-
-        FRACTION_LOADER_PROVIDER,
-        dumper(Fraction, Fraction.__str__),
-
-        COMPLEX_LOADER_PROVIDER,
-        dumper(complex, complex.__str__),
+        INT_PROVIDER,
+        FLOAT_PROVIDER,
+        STR_PROVIDER,
+        BOOL_PROVIDER,
+        DECIMAL_PROVIDER,
+        FRACTION_PROVIDER,
+        COMPLEX_PROVIDER,
 
         BytesBase64Provider(),
         BytesIOBase64Provider(),
@@ -188,10 +174,11 @@ def __init__(
         recipe: Iterable[Provider] = (),
         strict_coercion: bool = True,
         debug_trail: DebugTrail = DebugTrail.ALL,
+        hide_traceback: bool = True,
     ):
         self._strict_coercion = strict_coercion
         self._debug_trail = debug_trail
-        super().__init__(recipe)
+        super().__init__(recipe=recipe, hide_traceback=hide_traceback)
 
     def _calculate_derived(self):
         super()._calculate_derived()
@@ -203,31 +190,32 @@ def replace(
         *,
         strict_coercion: Optional[bool] = None,
         debug_trail: Optional[DebugTrail] = None,
+        hide_traceback: Optional[bool] = None,
     ) -> AR:
         with self._clone() as clone:
             if strict_coercion is not None:
                 clone._strict_coercion = strict_coercion
-
             if debug_trail is not None:
                 clone._debug_trail = debug_trail
-
+            if hide_traceback is not None:
+                clone._hide_traceback = hide_traceback
         return clone
 
     def extend(self: AR, *, recipe: Iterable[Provider]) -> AR:
         with self._clone() as clone:
-            clone._inc_instance_recipe = (
-                tuple(recipe) + clone._inc_instance_recipe
+            clone._instance_recipe = (
+                tuple(recipe) + clone._instance_recipe
             )
 
         return clone
 
-    def _get_config_recipe(self) -> VarTuple[Provider]:
+    def _get_recipe_tail(self) -> VarTuple[Provider]:
         return (
             ValueProvider(StrictCoercionRequest, self._strict_coercion),
             ValueProvider(DebugTrailRequest, self._debug_trail),
         )
 
-    def get_loader(self, tp: Type[T]) -> Loader[T]:
+    def get_loader(self, tp: type[T]) -> Loader[T]:
         try:
             return self._loader_cache[tp]
         except KeyError:
@@ -236,7 +224,7 @@ def get_loader(self, tp: Type[T]) -> Loader[T]:
         self._loader_cache[tp] = loader_
         return loader_
 
-    def _make_loader(self, tp: Type[T]) -> Loader[T]:
+    def _make_loader(self, tp: type[T]) -> Loader[T]:
         loader_ = self._facade_provide(
             LoaderRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
             error_message=f"Cannot produce loader for type {tp!r}",
@@ -253,7 +241,7 @@ def trail_rendering_wrapper(data):
 
         return loader_
 
-    def get_dumper(self, tp: Type[T]) -> Dumper[T]:
+    def get_dumper(self, tp: type[T]) -> Dumper[T]:
         try:
             return self._dumper_cache[tp]
         except KeyError:
@@ -262,7 +250,7 @@ def get_dumper(self, tp: Type[T]) -> Dumper[T]:
         self._dumper_cache[tp] = dumper_
         return dumper_
 
-    def _make_dumper(self, tp: Type[T]) -> Dumper[T]:
+    def _make_dumper(self, tp: type[T]) -> Dumper[T]:
         dumper_ = self._facade_provide(
             DumperRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
             error_message=f"Cannot produce dumper for type {tp!r}",
@@ -280,7 +268,7 @@ def trail_rendering_wrapper(data):
         return dumper_
 
     @overload
-    def load(self, data: Any, tp: Type[T], /) -> T:
+    def load(self, data: Any, tp: type[T], /) -> T:
         ...
 
     @overload
@@ -291,7 +279,7 @@ def load(self, data: Any, tp: TypeHint, /):
         return self.get_loader(tp)(data)
 
     @overload
-    def dump(self, data: T, tp: Type[T], /) -> Any:
+    def dump(self, data: T, tp: type[T], /) -> Any:
         ...
 
     @overload
diff --git a/src/adaptix/_internal/morphing/generic_provider.py b/src/adaptix/_internal/morphing/generic_provider.py
index 04926293..2b7cb3de 100644
--- a/src/adaptix/_internal/morphing/generic_provider.py
+++ b/src/adaptix/_internal/morphing/generic_provider.py
@@ -1,87 +1,66 @@
 import collections.abc
-from dataclasses import dataclass, replace
+from collections.abc import Collection, Iterable, Mapping, Sequence
+from dataclasses import dataclass
 from enum import Enum
 from os import PathLike
 from pathlib import Path
-from typing import Any, Collection, Dict, Iterable, Literal, Optional, Sequence, Set, Type, Union
+from typing import Any, Literal, Optional, TypeVar, Union
 
-from ..common import Dumper, Loader
+from ..common import Dumper, Loader, TypeHint
 from ..compat import CompatExceptionGroup
 from ..datastructures import ClassDispatcher
 from ..definitions import DebugTrail
-from ..feature_requirement import HAS_PY_39
 from ..provider.essential import CannotProvide, Mediator
-from ..provider.location import GenericParamLoc
-from ..provider.provider_template import for_predicate
-from ..provider.request_cls import (
-    DebugTrailRequest,
-    LocatedRequest,
-    LocStack,
-    StrictCoercionRequest,
-    TypeHintLoc,
-    get_type_from_request,
-    try_normalize_type,
-)
-from ..provider.static_provider import StaticProvider, static_provision_action
+from ..provider.loc_stack_filtering import LocStack
+from ..provider.located_request import LocatedRequestDelegatingProvider, LocatedRequestT, for_predicate
+from ..provider.location import GenericParamLoc, TypeHintLoc
 from ..special_cases_optimization import as_is_stub
 from ..type_tools import BaseNormType, NormTypeAlias, is_new_type, is_subclass_soft, strip_tags
+from ..utils import MappingHashWrapper
 from .load_error import BadVariantLoadError, LoadError, TypeLoadError, UnionLoadError
 from .provider_template import DumperProvider, LoaderProvider
-from .request_cls import DumperRequest, LoaderRequest
+from .request_cls import DebugTrailRequest, DumperRequest, LoaderRequest, StrictCoercionRequest
+from .utils import try_normalize_type
 
+ResponseT = TypeVar("ResponseT")
 
-class NewTypeUnwrappingProvider(StaticProvider):
-    @static_provision_action
-    def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
 
-        if not is_new_type(loc.type):
+class NewTypeUnwrappingProvider(LocatedRequestDelegatingProvider):
+    REQUEST_CLASSES = (LoaderRequest, DumperRequest)
+
+    def get_delegated_type(self, mediator: Mediator[LocatedRequestT], request: LocatedRequestT) -> TypeHint:
+        if not is_new_type(request.last_loc.type):
             raise CannotProvide
 
-        return mediator.delegating_provide(
-            replace(
-                request,
-                loc_stack=request.loc_stack.replace_last_type(loc.type.__supertype__),
-            ),
-        )
+        return request.last_loc.type.__supertype__
+
 
+class TypeHintTagsUnwrappingProvider(LocatedRequestDelegatingProvider):
+    REQUEST_CLASSES = (LoaderRequest, DumperRequest)
 
-class TypeHintTagsUnwrappingProvider(StaticProvider):
-    @static_provision_action
-    def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
-        norm = try_normalize_type(loc.type)
+    def get_delegated_type(self, mediator: Mediator[LocatedRequestT], request: LocatedRequestT) -> TypeHint:
+        tp = request.last_loc.type
+        norm = try_normalize_type(tp)
         unwrapped = strip_tags(norm)
-        if unwrapped.source == loc.type:  # type has not changed, continue search
+        if unwrapped.source == tp:  # type has not changed, continue search
             raise CannotProvide
 
-        return mediator.delegating_provide(
-            replace(
-                request,
-                loc_stack=request.loc_stack.replace_last_type(unwrapped.source),
-            ),
-        )
+        return unwrapped.source
 
 
-class TypeAliasUnwrappingProvider(StaticProvider):
-    @static_provision_action
-    def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
-        norm = try_normalize_type(loc.type)
+class TypeAliasUnwrappingProvider(LocatedRequestDelegatingProvider):
+    REQUEST_CLASSES = (LoaderRequest, DumperRequest)
+
+    def get_delegated_type(self, mediator: Mediator[LocatedRequestT], request: LocatedRequestT) -> TypeHint:
+        norm = try_normalize_type(request.last_loc.type)
         if not isinstance(norm, NormTypeAlias):
             raise CannotProvide
 
-        unwrapped = norm.value[tuple(arg.source for arg in norm.args)] if norm.args else norm.value
-        return mediator.delegating_provide(
-            replace(
-                request,
-                loc_stack=request.loc_stack.replace_last_type(unwrapped),
-            ),
-        )
+        return norm.value[tuple(arg.source for arg in norm.args)] if norm.args else norm.value
 
 
 def _is_exact_zero_or_one(arg):
-    return type(arg) is int and arg in (0, 1)  # noqa: E721
+    return type(arg) is int and arg in (0, 1)
 
 
 @dataclass
@@ -97,13 +76,13 @@ def _get_allowed_values_collection(self, args: Collection) -> Collection:
     def _get_allowed_values_repr(self, args: Collection, mediator: Mediator, loc_stack: LocStack) -> Collection:
         enum_cases = [arg for arg in args if isinstance(arg, Enum)]
         if not enum_cases:
-            return set(args)
+            return frozenset(args)
 
-        literal_dumper = self._provide_dumper(mediator, DumperRequest(loc_stack))
-        return {literal_dumper(arg) if isinstance(arg, Enum) else arg for arg in args}
+        literal_dumper = self.provide_dumper(mediator, DumperRequest(loc_stack))
+        return frozenset(literal_dumper(arg) if isinstance(arg, Enum) else arg for arg in args)
 
     def _get_enum_types(self, cases: Collection) -> Collection:
-        seen: Set[Type[Enum]] = set()
+        seen: set[type[Enum]] = set()
         enum_types = []
         for case in cases:
             case_type = type(case)
@@ -113,38 +92,57 @@ def _get_enum_types(self, cases: Collection) -> Collection:
         return enum_types
 
     def _fetch_enum_loaders(
-        self, mediator: Mediator, request: LoaderRequest, enum_classes: Iterable[Type[Enum]],
+        self,
+        mediator: Mediator,
+        request: LoaderRequest,
+        enum_classes: Iterable[type[Enum]],
     ) -> Iterable[Loader[Enum]]:
-        requests = [
-            LoaderRequest(
-                loc_stack=request.loc_stack.append_with(
-                    TypeHintLoc(type=enum_cls),
-                ),
-            ) for enum_cls in enum_classes
-        ]
+        requests = [request.append_loc(TypeHintLoc(type=enum_cls)) for enum_cls in enum_classes]
         return mediator.mandatory_provide_by_iterable(
             requests,
-            lambda: "Cannot create loaders for enum. Loader for literal cannot be created",
+            lambda: "Cannot create loader for literal. Loaders for enums cannot be created",
+        )
+
+    def _fetch_bytes_loader(
+        self,
+        mediator: Mediator,
+        request: LoaderRequest,
+    ) -> Loader[bytes]:
+        request = request.append_loc(TypeHintLoc(type=bytes))
+        return mediator.mandatory_provide(
+            request,
+            lambda _: "Cannot create loader for literal. Loader for bytes cannot be created",
         )
 
     def _fetch_enum_dumpers(
-        self, mediator: Mediator, request: DumperRequest, enum_classes: Iterable[Type[Enum]],
-    ) -> Dict[Type[Enum], Dumper[Enum]]:
-        requests = [
-            DumperRequest(
-                loc_stack=request.loc_stack.append_with(
-                    TypeHintLoc(type=enum_cls),
-                ),
-            ) for enum_cls in enum_classes
-        ]
+        self,
+        mediator: Mediator,
+        request: DumperRequest,
+        enum_classes: Iterable[type[Enum]],
+    ) -> Mapping[type[Enum], Dumper[Enum]]:
+        requests = [request.append_loc(TypeHintLoc(type=enum_cls)) for enum_cls in enum_classes]
         dumpers = mediator.mandatory_provide_by_iterable(
             requests,
-            lambda: "Cannot create loaders for enum. Loader for literal cannot be created",
+            lambda: "Cannot create dumper for literal. Dumpers for enums cannot be created",
         )
         return dict(zip(enum_classes, dumpers))
 
+    def _fetch_bytes_dumper(
+        self,
+        mediator: Mediator,
+        request: DumperRequest,
+    ) -> Dumper[bytes]:
+        request = request.append_loc(TypeHintLoc(type=bytes))
+        return mediator.mandatory_provide(
+            request,
+            lambda _: "Cannot create dumper for literal. Dumper for bytes cannot be created",
+        )
+
     def _get_literal_loader_with_enum(  # noqa: C901
-        self, basic_loader: Loader, enum_loaders: Sequence[Loader[Enum]], allowed_values: Collection,
+        self,
+        basic_loader: Loader,
+        enum_loaders: Sequence[Loader[Enum]],
+        allowed_values: Collection,
     ) -> Loader:
         if not enum_loaders:
             return basic_loader
@@ -177,20 +175,71 @@ def wrapped_loader_with_enums(data):
 
         return wrapped_loader_with_enums
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        norm = try_normalize_type(get_type_from_request(request))
-        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+    def _get_literal_loader_with_bytes(
+        self,
+        basic_loader: Loader,
+        allowed_values: Collection,
+        bytes_loader: Loader,
+    ) -> Loader:
+        def wrapped_loader_with_bytes(data):
+            try:
+                bytes_value = bytes_loader(data)
+            except LoadError:
+                pass
+            else:
+                if bytes_value in allowed_values:
+                    return bytes_value
+            return basic_loader(data)
 
-        enum_cases = [arg for arg in norm.args if isinstance(arg, Enum)]
-        enum_loaders = list(self._fetch_enum_loaders(mediator, request, self._get_enum_types(enum_cases)))
+        return wrapped_loader_with_bytes
+
+    def _get_literal_loader_many(self, *loaders: Loader, basic_loader: Loader) -> Loader:
+        if len(loaders) == 1:
+            return loaders[0]
+
+        def wrapped_loader_many(data):
+            for c, loader in enumerate(loaders):
+                try:
+                    return loader(data)
+                except LoadError:
+                    last_iteration = len(loaders) - 1
+                    if c != last_iteration:
+                        continue
+            return basic_loader(data)
+
+        return wrapped_loader_many
+
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        norm = try_normalize_type(request.last_loc.type)
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+        enum_cases = tuple(arg for arg in norm.args if isinstance(arg, Enum))
+        bytes_cases = tuple(arg for arg in norm.args if isinstance(arg, bytes))
+        enum_loaders = tuple(self._fetch_enum_loaders(mediator, request, self._get_enum_types(enum_cases)))
+        bytes_loader = self._fetch_bytes_loader(mediator, request)
         allowed_values_repr = self._get_allowed_values_repr(norm.args, mediator, request.loc_stack)
+        return mediator.cached_call(
+            self._make_loader,
+            cases=norm.args,
+            bytes_cases=bytes_cases,
+            strict_coercion=strict_coercion,
+            enum_loaders=enum_loaders,
+            bytes_loader=bytes_loader,
+            allowed_values_repr=allowed_values_repr,
+        )
 
-        if strict_coercion and any(
-            isinstance(arg, bool) or _is_exact_zero_or_one(arg)
-            for arg in norm.args
-        ):
+    def _make_loader(
+        self,
+        *,
+        cases: Sequence[Any],
+        strict_coercion: bool,
+        enum_loaders: Sequence[Loader],
+        allowed_values_repr: Collection[str],
+        bytes_cases: Sequence[bytes],
+        bytes_loader: Loader[bytes],
+    ) -> Loader:
+        if strict_coercion and any(isinstance(arg, bool) or _is_exact_zero_or_one(arg) for arg in cases):
             allowed_values_with_types = self._get_allowed_values_collection(
-                [(type(el), el) for el in norm.args],
+                [(type(el), el) for el in cases],
             )
 
             # since True == 1 and False == 0
@@ -200,27 +249,48 @@ def literal_loader_sc(data):
                 raise BadVariantLoadError(allowed_values_repr, data)
 
             return self._get_literal_loader_with_enum(
-                literal_loader_sc, enum_loaders, allowed_values_with_types,
+                literal_loader_sc,
+                enum_loaders,
+                allowed_values_with_types,
             )
 
-        allowed_values = self._get_allowed_values_collection(norm.args)
+        allowed_values = self._get_allowed_values_collection(cases)
 
         def literal_loader(data):
             if data in allowed_values:
                 return data
             raise BadVariantLoadError(allowed_values_repr, data)
 
-        return self._get_literal_loader_with_enum(literal_loader, enum_loaders, allowed_values)
+        if bytes_cases and not enum_loaders:
+            return self._get_literal_loader_with_bytes(literal_loader, allowed_values, bytes_loader)
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        norm = try_normalize_type(get_type_from_request(request))
+        if not bytes_cases:
+            return self._get_literal_loader_with_enum(literal_loader, enum_loaders, allowed_values)
+
+        return self._get_literal_loader_many(
+            self._get_literal_loader_with_bytes(literal_loader, allowed_values, bytes_loader),
+            self._get_literal_loader_with_enum(literal_loader, enum_loaders, allowed_values),
+            basic_loader=literal_loader,
+        )
+
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        norm = try_normalize_type(request.last_loc.type)
         enum_cases = [arg for arg in norm.args if isinstance(arg, Enum)]
+        bytes_cases = tuple(arg for arg in norm.args if isinstance(arg, bytes))
 
-        if not enum_cases:
+        if not enum_cases and not bytes_cases:
             return as_is_stub
 
         enum_dumpers = self._fetch_enum_dumpers(mediator, request, self._get_enum_types(enum_cases))
+        bytes_dumper = self._fetch_bytes_dumper(mediator, request)
 
+        return mediator.cached_call(
+            self._make_dumper,
+            enum_dumpers_wrapper=MappingHashWrapper(enum_dumpers),
+            bytes_dumper=bytes_dumper,
+        )
+
+    def _get_enum_dumper(self, enum_dumpers: Mapping[type[Enum], Dumper[Enum]]) -> Dumper:
         if len(enum_dumpers) == 1:
             enum_dumper = next(iter(enum_dumpers.values()))
 
@@ -238,40 +308,69 @@ def literal_dumper_with_enums(data):
 
         return literal_dumper_with_enums
 
+    def _get_bytes_literal_dumper(self, bytes_dumper: Dumper[bytes]) -> Dumper:
+        def literal_dumper_with_bytes(data):
+            if isinstance(data, bytes):
+                return bytes_dumper(data)
+            return data
+
+        return literal_dumper_with_bytes
+
+    def _make_dumper(
+        self,
+        enum_dumpers_wrapper: MappingHashWrapper[Mapping[type[Enum], Dumper[Enum]]],
+        bytes_dumper: Optional[Dumper[bytes]],
+    ):
+        enum_dumpers = enum_dumpers_wrapper.mapping
+
+        if not bytes_dumper:
+            return self._get_enum_dumper(enum_dumpers)
+
+        if not enum_dumpers:
+            return self._get_bytes_literal_dumper(bytes_dumper)
+
+        bytes_literal_dumper = self._get_bytes_literal_dumper(bytes_dumper)
+        enum_literal_dumper = self._get_enum_dumper(enum_dumpers)
+
+        def literal_dumper_many(data):
+            if isinstance(data, bytes):
+                return bytes_literal_dumper(data)
+            if isinstance(data, Enum):
+                return enum_literal_dumper(data)
+            return data
+
+        return literal_dumper_many
+
 
 @for_predicate(Union)
 class UnionProvider(LoaderProvider, DumperProvider):
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        norm = try_normalize_type(get_type_from_request(request))
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        norm = try_normalize_type(request.last_loc.type)
         debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
 
         if self._is_single_optional(norm):
             not_none = next(case for case in norm.args if case.origin is not None)
             not_none_loader = mediator.mandatory_provide(
-                LoaderRequest(
-                    loc_stack=request.loc_stack.append_with(
-                        GenericParamLoc(
-                            type=not_none.source,
-                            generic_pos=0,
-                        ),
+                request.append_loc(
+                    GenericParamLoc(
+                        type=not_none.source,
+                        generic_pos=0,
                     ),
                 ),
                 lambda x: "Cannot create loader for union. Loaders for some union cases cannot be created",
             )
             if debug_trail in (DebugTrail.ALL, DebugTrail.FIRST):
-                return self._single_optional_dt_loader(norm.source, not_none_loader)
+                return mediator.cached_call(self._single_optional_dt_loader, norm.source, not_none_loader)
             if debug_trail == DebugTrail.DISABLE:
-                return self._single_optional_dt_disable_loader(not_none_loader)
+                return mediator.cached_call(self._single_optional_dt_disable_loader, not_none_loader)
             raise ValueError
 
         loaders = mediator.mandatory_provide_by_iterable(
             [
-                LoaderRequest(
-                    loc_stack=request.loc_stack.append_with(
-                        GenericParamLoc(
-                            type=tp.source,
-                            generic_pos=i,
-                        ),
+                request.append_loc(
+                    GenericParamLoc(
+                        type=tp.source,
+                        generic_pos=i,
                     ),
                 )
                 for i, tp in enumerate(norm.args)
@@ -279,11 +378,11 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             lambda: "Cannot create loader for union. Loaders for some union cases cannot be created",
         )
         if debug_trail == DebugTrail.DISABLE:
-            return self._get_loader_dt_disable(tuple(loaders))
+            return mediator.cached_call(self._get_loader_dt_disable, tuple(loaders))
         if debug_trail == DebugTrail.FIRST:
-            return self._get_loader_dt_first(norm.source, tuple(loaders))
+            return mediator.cached_call(self._get_loader_dt_first, norm.source, tuple(loaders))
         if debug_trail == DebugTrail.ALL:
-            return self._get_loader_dt_all(norm.source, tuple(loaders))
+            return mediator.cached_call(self._get_loader_dt_all, norm.source, tuple(loaders))
         raise ValueError
 
     def _single_optional_dt_disable_loader(self, loader: Loader) -> Loader:
@@ -357,31 +456,27 @@ def _is_single_optional(self, norm: BaseNormType) -> bool:
     def _is_class_origin(self, origin) -> bool:
         return (origin is None or isinstance(origin, type)) and not is_subclass_soft(origin, collections.abc.Callable)
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        request_type = get_type_from_request(request)
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        request_type = request.last_loc.type
         norm = try_normalize_type(request_type)
 
         if self._is_single_optional(norm):
             not_none = next(case for case in norm.args if case.origin is not None)
             not_none_dumper = mediator.mandatory_provide(
-                DumperRequest(
-                    loc_stack=request.loc_stack.append_with(
-                        GenericParamLoc(
-                            type=not_none.source,
-                            generic_pos=0,
-                        ),
+                request.append_loc(
+                    GenericParamLoc(
+                        type=not_none.source,
+                        generic_pos=0,
                     ),
                 ),
                 lambda x: "Cannot create dumper for union. Dumpers for some union cases cannot be created",
             )
             if not_none_dumper == as_is_stub:
                 return as_is_stub
-            return self._get_single_optional_dumper(not_none_dumper)
+            return mediator.cached_call(self._get_single_optional_dumper, not_none_dumper)
 
         forbidden_origins = [
-            case.source
-            for case in norm.args
-            if not self._is_class_origin(case.origin) and case.origin != Literal
+            case.source for case in norm.args if not self._is_class_origin(case.origin) and case.origin != Literal
         ]
 
         if forbidden_origins:
@@ -393,12 +488,10 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
 
         dumpers = mediator.mandatory_provide_by_iterable(
             [
-                DumperRequest(
-                    loc_stack=request.loc_stack.append_with(
-                        GenericParamLoc(
-                            type=tp.source,
-                            generic_pos=i,
-                        ),
+                request.append_loc(
+                    GenericParamLoc(
+                        type=tp.source,
+                        generic_pos=i,
                     ),
                 )
                 for i, tp in enumerate(norm.args)
@@ -408,6 +501,9 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         if all(dumper == as_is_stub for dumper in dumpers):
             return as_is_stub
 
+        return mediator.cached_call(self._make_dumper, norm, tuple(dumpers))
+
+    def _make_dumper(self, norm: BaseNormType, dumpers: Iterable[Dumper]) -> Dumper:
         dumper_type_dispatcher = ClassDispatcher(
             {type(None) if case.origin is None else case.origin: dumper for case, dumper in zip(norm.args, dumpers)},
         )
@@ -446,9 +542,7 @@ def _get_dumper_for_literal(
     ) -> Optional[Dumper]:
         try:
             literal_type, literal_dumper = next(
-                (union_case, dumper) for union_case, dumper
-                in zip(norm.args, dumpers)
-                if union_case.origin is Literal
+                (union_case, dumper) for union_case, dumper in zip(norm.args, dumpers) if union_case.origin is Literal
             )
         except StopIteration:
             return None
@@ -468,11 +562,11 @@ def path_like_dumper(data):
     return data.__fspath__()
 
 
-@for_predicate(PathLike[str] if HAS_PY_39 else PathLike)
+@for_predicate(PathLike[str])
 class PathLikeProvider(LoaderProvider, DumperProvider):
     _impl = Path
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         return mediator.mandatory_provide(
             LoaderRequest(
                 loc_stack=request.loc_stack.replace_last_type(self._impl),
@@ -480,5 +574,5 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             lambda x: f"Cannot create loader for {PathLike}. Loader for {Path} cannot be created",
         )
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         return path_like_dumper
diff --git a/src/adaptix/_internal/morphing/iterable_provider.py b/src/adaptix/_internal/morphing/iterable_provider.py
index abdca17b..4c77030b 100644
--- a/src/adaptix/_internal/morphing/iterable_provider.py
+++ b/src/adaptix/_internal/morphing/iterable_provider.py
@@ -1,31 +1,29 @@
 # ruff: noqa: SIM113
 import collections.abc
+from collections.abc import Iterable, Mapping
 from inspect import isabstract
-from typing import Callable, Iterable, Mapping
+from typing import Callable
 
 from ..common import Dumper, Loader
 from ..compat import CompatExceptionGroup
 from ..definitions import DebugTrail
-from ..morphing.provider_template import DumperProvider, LoaderProvider
+from ..morphing.provider_template import MorphingProvider
 from ..provider.essential import CannotProvide, Mediator
+from ..provider.located_request import LocatedRequest, for_predicate
 from ..provider.location import GenericParamLoc
-from ..provider.provider_template import for_predicate
-from ..provider.request_cls import (
-    DebugTrailRequest,
-    LocatedRequest,
-    StrictCoercionRequest,
-    get_type_from_request,
-    try_normalize_type,
-)
 from ..struct_trail import append_trail, render_trail_as_note
+from .json_schema.definitions import JSONSchema
+from .json_schema.request_cls import JSONSchemaRequest
+from .json_schema.schema_model import JSONSchemaType
 from .load_error import AggregateLoadError, ExcludedTypeLoadError, LoadError, TypeLoadError
-from .request_cls import DumperRequest, LoaderRequest
+from .request_cls import DebugTrailRequest, DumperRequest, LoaderRequest, StrictCoercionRequest
+from .utils import try_normalize_type
 
 CollectionsMapping = collections.abc.Mapping
 
 
 @for_predicate(Iterable)
-class IterableProvider(LoaderProvider, DumperProvider):
+class IterableProvider(MorphingProvider):
     ABC_TO_IMPL = {
         collections.abc.Iterable: tuple,
         collections.abc.Reversible: tuple,
@@ -51,9 +49,9 @@ def _get_iter_factory(self, origin) -> Callable[[Iterable], Iterable]:
         raise CannotProvide
 
     def _fetch_norm_and_arg(self, request: LocatedRequest):
-        norm = try_normalize_type(get_type_from_request(request))
+        norm = try_normalize_type(request.last_loc.type)
 
-        if len(norm.args) != 1 and not (norm.origin == tuple and norm.args[-1] == Ellipsis):
+        if len(norm.args) != 1 and not (norm.origin is tuple and norm.args[-1] == Ellipsis):
             raise CannotProvide
 
         try:
@@ -66,24 +64,18 @@ def _fetch_norm_and_arg(self, request: LocatedRequest):
 
         return norm, arg
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         norm, arg = self._fetch_norm_and_arg(request)
 
         iter_factory = self._get_iter_factory(norm.origin)
         arg_loader = mediator.mandatory_provide(
-            LoaderRequest(
-                loc_stack=request.loc_stack.append_with(
-                    GenericParamLoc(
-                        type=arg,
-                        generic_pos=0,
-                    ),
-                ),
-            ),
+            request.append_loc(GenericParamLoc(type=arg, generic_pos=0)),
             lambda x: "Cannot create loader for iterable. Loader for element cannot be created",
         )
         strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
         debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
-        return self._make_loader(
+        return mediator.cached_call(
+            self._make_loader,
             origin=norm.origin,
             iter_factory=iter_factory,
             arg_loader=arg_loader,
@@ -168,7 +160,7 @@ def _get_dt_sc_loader(self, iter_factory, iter_mapper):
         def iter_loader_dt_sc(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(Iterable, Mapping, data)
-            if type(data) is str:  # noqa: E721
+            if type(data) is str:
                 raise ExcludedTypeLoadError(Iterable, str, data)
 
             try:
@@ -184,7 +176,7 @@ def _get_dt_disable_sc_loader(self, iter_factory, arg_loader):
         def iter_loader_sc(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(Iterable, Mapping, data)
-            if type(data) is str:  # noqa: E721
+            if type(data) is str:
                 raise ExcludedTypeLoadError(Iterable, str, data)
 
             try:
@@ -207,23 +199,17 @@ def iter_loader(data):
 
         return iter_loader
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         norm, arg = self._fetch_norm_and_arg(request)
 
         iter_factory = self._get_iter_factory(norm.origin)
         arg_dumper = mediator.mandatory_provide(
-            DumperRequest(
-                loc_stack=request.loc_stack.append_with(
-                    GenericParamLoc(
-                        type=arg,
-                        generic_pos=0,
-                    ),
-                ),
-            ),
+            request.append_loc(GenericParamLoc(type=arg, generic_pos=0)),
             lambda x: "Cannot create dumper for iterable. Dumper for element cannot be created",
         )
         debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
-        return self._make_dumper(
+        return mediator.cached_call(
+            self._make_dumper,
             origin=norm.origin,
             iter_factory=iter_factory,
             arg_dumper=arg_dumper,
@@ -286,3 +272,18 @@ def iter_dumper(data):
             return iter_factory(map(arg_dumper, data))
 
         return iter_dumper
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        norm, arg = self._fetch_norm_and_arg(request)
+        item_schema = mediator.mandatory_provide(
+            request.append_loc(
+                GenericParamLoc(
+                    type=arg,
+                    generic_pos=0,
+                ),
+            ),
+            lambda x: "Cannot create JSONSchema for iterable. JSONSchema for element cannot be created",
+        )
+        if norm.origin is set:
+            return JSONSchema(type=JSONSchemaType.ARRAY, items=item_schema, unique_items=True)
+        return JSONSchema(type=JSONSchemaType.ARRAY, items=item_schema)
diff --git a/src/adaptix/_internal/morphing/json_schema/__init__.py b/src/adaptix/_internal/morphing/json_schema/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/adaptix/_internal/morphing/json_schema/definitions.py b/src/adaptix/_internal/morphing/json_schema/definitions.py
new file mode 100644
index 00000000..8b0465bb
--- /dev/null
+++ b/src/adaptix/_internal/morphing/json_schema/definitions.py
@@ -0,0 +1,32 @@
+from dataclasses import dataclass, field
+from typing import Generic, TypeVar, Union
+
+from ...provider.loc_stack_filtering import LocStack
+from .schema_model import BaseJSONSchema
+
+T = TypeVar("T")
+
+JSONSchemaT = TypeVar("JSONSchemaT")
+
+
+@dataclass(frozen=True)
+class JSONSchemaRef(Generic[JSONSchemaT]):
+    value: str
+    is_final: bool
+    json_schema: JSONSchemaT = field(repr=False)
+    loc_stack: LocStack = field(repr=False)
+
+    def __hash__(self):
+        return hash(self.value)
+
+
+Boolable = Union[T, bool]
+
+
+class JSONSchema(BaseJSONSchema[JSONSchemaRef[Boolable["JSONSchema"]], Boolable["JSONSchema"]]):
+    pass
+
+
+class ResolvedJSONSchema(BaseJSONSchema[str, Boolable["ResolvedJSONSchema"]]):
+    pass
+
diff --git a/src/adaptix/_internal/morphing/json_schema/providers.py b/src/adaptix/_internal/morphing/json_schema/providers.py
new file mode 100644
index 00000000..9b5d0c09
--- /dev/null
+++ b/src/adaptix/_internal/morphing/json_schema/providers.py
@@ -0,0 +1,45 @@
+from ...provider.essential import Mediator
+from ...provider.located_request import LocatedRequestMethodsProvider
+from ...provider.methods_provider import method_handler
+from .definitions import JSONSchemaRef
+from .request_cls import InlineJSONSchemaRequest, JSONSchemaRefRequest
+
+
+class InlineJSONSchemaProvider(LocatedRequestMethodsProvider):
+    def __init__(self, *, inline: bool):
+        self._inline = inline
+
+    @method_handler
+    def provide_inline_json_schema(self, mediator: Mediator, request: InlineJSONSchemaRequest) -> bool:
+        return self._inline
+
+
+class JSONSchemaRefProvider(LocatedRequestMethodsProvider):
+    def __init__(self, *, inline: bool):
+        self._inline = inline
+
+    @method_handler
+    def provide_json_schema_ref(self, mediator: Mediator, request: JSONSchemaRefRequest) -> JSONSchemaRef:
+        return JSONSchemaRef(
+            value=self._get_reference_value(request),
+            is_final=False,
+            json_schema=request.json_schema,
+            loc_stack=request.loc_stack,
+        )
+
+    def _get_reference_value(self, request: JSONSchemaRefRequest) -> str:
+        return str(request.loc_stack.last.type)
+
+
+class ConstantJSONSchemaRefProvider(LocatedRequestMethodsProvider):
+    def __init__(self, ref_value: str):
+        self._ref_value = ref_value
+
+    @method_handler
+    def provide_json_schema_ref(self, mediator: Mediator, request: JSONSchemaRefRequest) -> JSONSchemaRef:
+        return JSONSchemaRef(
+            value=self._ref_value,
+            is_final=True,
+            json_schema=request.json_schema,
+            loc_stack=request.loc_stack,
+        )
diff --git a/src/adaptix/_internal/morphing/json_schema/request_cls.py b/src/adaptix/_internal/morphing/json_schema/request_cls.py
new file mode 100644
index 00000000..bb4b940f
--- /dev/null
+++ b/src/adaptix/_internal/morphing/json_schema/request_cls.py
@@ -0,0 +1,31 @@
+from dataclasses import dataclass
+
+from ...definitions import Direction
+from ...provider.located_request import LocatedRequest
+from .definitions import JSONSchema, JSONSchemaRef
+
+
+@dataclass(frozen=True)
+class JSONSchemaContext:
+    dialect: str
+    direction: Direction
+
+
+@dataclass(frozen=True)
+class WithJSONSchemaContext:
+    ctx: JSONSchemaContext
+
+
+@dataclass(frozen=True)
+class JSONSchemaRequest(LocatedRequest[JSONSchema], WithJSONSchemaContext):
+    pass
+
+
+@dataclass(frozen=True)
+class JSONSchemaRefRequest(LocatedRequest[JSONSchemaRef], WithJSONSchemaContext):
+    json_schema: JSONSchema
+
+
+@dataclass(frozen=True)
+class InlineJSONSchemaRequest(LocatedRequest[bool], WithJSONSchemaContext):
+    pass
diff --git a/src/adaptix/_internal/morphing/json_schema/schema_model.py b/src/adaptix/_internal/morphing/json_schema/schema_model.py
new file mode 100644
index 00000000..0997d8e0
--- /dev/null
+++ b/src/adaptix/_internal/morphing/json_schema/schema_model.py
@@ -0,0 +1,164 @@
+from collections.abc import Mapping, Sequence
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import Generic, TypeVar, Union
+
+from ...utils import Omittable, Omitted
+
+T = TypeVar("T")
+
+JSONNumeric = Union[int, float]
+JSONObject = Mapping[str, T]
+JSONValue = Union[
+    JSONNumeric,
+    str,
+    bool,
+    None,
+    Sequence["JSONValue"],
+    JSONObject["JSONValue"],
+]
+
+
+class JSONSchemaType(Enum):
+    NULL = "null"
+    BOOLEAN = "boolean"
+    OBJECT = "object"
+    ARRAY = "array"
+    NUMBER = "number"
+    INTEGER = "integer"
+    STRING = "string"
+
+
+class JSONSchemaBuiltinFormat(Enum):
+    DATE_TIME = "date-time"
+    DATE = "date"
+    TIME = "time"
+    DURATION = "duration"
+    EMAIL = "email"
+    IDN_EMAIL = "idn-email"
+    HOSTNAME = "hostname"
+    IDN_HOSTNAME = "idn-hostname"
+    IPV4 = "ipv4"
+    IPV6 = "ipv6"
+    URI = "uri"
+    URI_REFERENCE = "uri-reference"
+    IRI = "iri"
+    IRI_REFERENCE = "iri-reference"
+    UUID = "uuid"
+    URI_TEMPLATE = "uri-template"
+    JSON_POINTER = "json-pointer"
+    RELATIVE_JSON_POINTER = "relative-json-pointer"
+    REGEX = "regex"
+
+
+JSONSchemaT = TypeVar("JSONSchemaT")
+RefT = TypeVar("RefT")
+
+
+@dataclass
+class _JSONSchemaCore(Generic[RefT, JSONSchemaT]):
+    schema: Omittable[str] = Omitted()
+    vocabulary: Omittable[JSONObject[bool]] = Omitted()
+    id: Omittable[str] = Omitted()
+    anchor: Omittable[str] = Omitted()
+    dynamic_anchor: Omittable[str] = Omitted()
+    ref: Omittable[RefT] = Omitted()
+    dynamic_ref: Omittable[str] = Omitted()
+    defs: Omittable[JSONObject[JSONSchemaT]] = Omitted()
+    comment: Omittable[str] = Omitted()
+
+
+@dataclass
+class _JSONSchemaSubschemas(Generic[JSONSchemaT]):
+    # combinators
+    all_of: Omittable[Sequence[JSONSchemaT]] = Omitted()
+    any_of: Omittable[Sequence[JSONSchemaT]] = Omitted()
+    one_of: Omittable[Sequence[JSONSchemaT]] = Omitted()
+    not_: Omittable[JSONSchemaT] = Omitted()
+
+    # conditions
+    if_: Omittable[JSONSchemaT] = Omitted()
+    then: Omittable[JSONSchemaT] = Omitted()
+    else_: Omittable[JSONSchemaT] = Omitted()
+    dependent_schemas: Omittable[JSONObject[JSONSchemaT]] = Omitted()
+
+    # array
+    prefix_items: Omittable[Sequence[JSONSchemaT]] = Omitted()
+    items: Omittable[JSONSchemaT] = Omitted()
+    contains: Omittable[JSONSchemaT] = Omitted()
+
+    # object
+    properties: Omittable[JSONObject[JSONSchemaT]] = Omitted()
+    pattern_properties: Omittable[JSONObject[JSONSchemaT]] = Omitted()
+    additional_properties: Omittable[JSONSchemaT] = Omitted()
+    property_names: Omittable[JSONSchemaT] = Omitted()
+
+    # Unevaluated Locations
+    unevaluated_items: Omittable[JSONObject[JSONSchemaT]] = Omitted()
+    unevaluated_properties: Omittable[JSONObject[JSONSchemaT]] = Omitted()
+
+
+@dataclass
+class _JSONSchemaValidation(Generic[JSONSchemaT]):
+    # common
+    type: Omittable[Union[JSONSchemaType, Sequence[JSONSchemaType]]] = Omitted()
+    enum: Omittable[Sequence[JSONValue]] = Omitted()
+    const: Omittable[JSONValue] = Omitted()
+
+    format: Omittable[Union[JSONSchemaBuiltinFormat, str]] = Omitted()
+
+    # numeric
+    multiple_of: Omittable[JSONNumeric] = Omitted()
+    maximum: Omittable[JSONNumeric] = Omitted()
+    exclusive_maximum: Omittable[JSONNumeric] = Omitted()
+    minimum: Omittable[JSONNumeric] = Omitted()
+    exclusive_minimum: Omittable[JSONNumeric] = Omitted()
+
+    # string
+    max_length: Omittable[int] = Omitted()
+    min_length: Omittable[int] = Omitted()
+    pattern: Omittable[str] = Omitted()
+
+    content_encoding: Omittable[str] = Omitted()
+    content_media_type: Omittable[str] = Omitted()
+    content_schema: Omittable[JSONSchemaT] = Omitted()
+
+    # array
+    max_items: Omittable[int] = Omitted()
+    min_items: Omittable[int] = Omitted()
+    unique_items: Omittable[bool] = Omitted()
+    max_contains: Omittable[int] = Omitted()
+    min_contains: Omittable[int] = Omitted()
+
+    # object
+    max_properties: Omittable[int] = Omitted()
+    min_properties: Omittable[int] = Omitted()
+    required: Omittable[Sequence[str]] = Omitted()
+    dependent_required: Omittable[JSONObject[Sequence[str]]] = Omitted()
+
+
+@dataclass
+class _JSONSchemaAnnotations:
+    title: Omittable[str] = Omitted()
+    description: Omittable[str] = Omitted()
+    default: Omittable[JSONValue] = Omitted()
+    deprecated: Omittable[bool] = Omitted()
+    read_only: Omittable[bool] = Omitted()
+    write_only: Omittable[bool] = Omitted()
+    examples: Omittable[Sequence[JSONValue]] = Omitted()
+
+
+@dataclass
+class BaseJSONSchema(
+    _JSONSchemaCore[RefT, JSONSchemaT],
+    _JSONSchemaSubschemas[JSONSchemaT],
+    _JSONSchemaValidation[JSONSchemaT],
+    _JSONSchemaAnnotations,
+    Generic[RefT, JSONSchemaT],
+):
+    extra_keywords: JSONObject[JSONValue] = field(default_factory=dict)
+
+
+class JSONSchemaDialect(str, Enum):
+    DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema"
+
diff --git a/src/adaptix/_internal/morphing/load_error.py b/src/adaptix/_internal/morphing/load_error.py
index 020c567c..70f39697 100644
--- a/src/adaptix/_internal/morphing/load_error.py
+++ b/src/adaptix/_internal/morphing/load_error.py
@@ -1,7 +1,8 @@
 import dataclasses
+from collections.abc import Iterable
 from dataclasses import dataclass
 from functools import partial
-from typing import Any, Iterable, Optional, Union
+from typing import Any, Optional, Union
 
 from ..common import TypeHint, VarTuple
 from ..compat import CompatExceptionGroup
diff --git a/src/adaptix/_internal/morphing/model/basic_gen.py b/src/adaptix/_internal/morphing/model/basic_gen.py
index a5dd30af..82efa7ae 100644
--- a/src/adaptix/_internal/morphing/model/basic_gen.py
+++ b/src/adaptix/_internal/morphing/model/basic_gen.py
@@ -1,29 +1,17 @@
 import itertools
 from abc import ABC, abstractmethod
+from collections.abc import Collection, Container, Iterable, Mapping, Set
 from dataclasses import dataclass
-from typing import (
-    AbstractSet,
-    Any,
-    Callable,
-    Collection,
-    Container,
-    Dict,
-    Iterable,
-    List,
-    Mapping,
-    Set,
-    Tuple,
-    TypeVar,
-    Union,
-)
+from typing import Any, Callable, TypeVar, Union
 
 from ...code_tools.code_builder import CodeBuilder
 from ...code_tools.compiler import ClosureCompiler
 from ...code_tools.utils import get_literal_expr
 from ...model_tools.definitions import InputField, OutputField
 from ...provider.essential import CannotProvide, Mediator
-from ...provider.request_cls import LocatedRequest, LocStack
-from ...provider.static_provider import StaticProvider, static_provision_action
+from ...provider.loc_stack_filtering import LocStack
+from ...provider.located_request import LocatedRequest
+from ...provider.methods_provider import MethodsProvider, method_handler
 from .crown_definitions import (
     BaseCrown,
     BaseDictCrown,
@@ -46,7 +34,7 @@
 
 @dataclass
 class CodeGenHookData:
-    namespace: Dict[str, Any]
+    namespace: dict[str, Any]
     source: str
 
 
@@ -69,13 +57,13 @@ def fetch_code_gen_hook(mediator: Mediator, loc_stack: LocStack) -> CodeGenHook:
         return stub_code_gen_hook
 
 
-class CodeGenAccumulator(StaticProvider):
+class CodeGenAccumulator(MethodsProvider):
     """Accumulates all generated code. It may be useful for debugging"""
 
     def __init__(self) -> None:
-        self.list: List[Tuple[CodeGenHookRequest, CodeGenHookData]] = []
+        self.list: list[tuple[CodeGenHookRequest, CodeGenHookData]] = []
 
-    @static_provision_action
+    @method_handler
     def _provide_code_gen_hook(self, mediator: Mediator, request: CodeGenHookRequest) -> CodeGenHook:
         def hook(data: CodeGenHookData):
             self.list.append((request, data))
@@ -119,7 +107,7 @@ def _inner_collect_used_direct_fields(crown: BaseCrown) -> Iterable[str]:
     raise TypeError
 
 
-def _collect_used_direct_fields(crown: BaseCrown) -> Set[str]:
+def _collect_used_direct_fields(crown: BaseCrown) -> set[str]:
     lst = _inner_collect_used_direct_fields(crown)
 
     used_set = set()
@@ -131,7 +119,7 @@ def _collect_used_direct_fields(crown: BaseCrown) -> Set[str]:
     return used_set
 
 
-def get_skipped_fields(shape: BaseShape, name_layout: BaseNameLayout) -> AbstractSet[str]:
+def get_skipped_fields(shape: BaseShape, name_layout: BaseNameLayout) -> Set[str]:
     used_direct_fields = _collect_used_direct_fields(name_layout.crown)
     extra_targets = name_layout.extra_move.fields if isinstance(name_layout.extra_move, ExtraTargets) else ()
     return {
@@ -258,11 +246,11 @@ def has_collect_policy(crown: InpCrown) -> bool:
 
 class ModelLoaderGen(ABC):
     @abstractmethod
-    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, closure_name: str) -> tuple[str, Mapping[str, object]]:
         ...
 
 
 class ModelDumperGen(ABC):
     @abstractmethod
-    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, closure_name: str) -> tuple[str, Mapping[str, object]]:
         ...
diff --git a/src/adaptix/_internal/morphing/model/crown_definitions.py b/src/adaptix/_internal/morphing/model/crown_definitions.py
index b4f2d299..3a814b13 100644
--- a/src/adaptix/_internal/morphing/model/crown_definitions.py
+++ b/src/adaptix/_internal/morphing/model/crown_definitions.py
@@ -1,10 +1,11 @@
+from collections.abc import Mapping, Sequence
 from dataclasses import dataclass
-from typing import Any, Callable, Dict, Generic, Mapping, Sequence, TypeVar, Union
+from typing import Any, Callable, Generic, TypeVar, Union
 
 from ...common import VarTuple
 from ...model_tools.definitions import BaseShape, DefaultFactory, DefaultValue, InputShape, OutputShape
-from ...provider.request_cls import LocatedRequest
-from ...utils import SingletonMeta
+from ...provider.located_request import LocatedRequest
+from ...utils import MappingHashWrapper, SingletonMeta
 
 T = TypeVar("T")
 
@@ -32,25 +33,25 @@ class ExtraCollect(metaclass=SingletonMeta):
 # as well as the policy of extra data processing.
 # This structure is named in honor of the crown of the tree.
 #
-# NoneCrown-s represent element that do not map to any field
+# NoneCrown-s represents an element that does not map to any field
 
 
-@dataclass
+@dataclass(frozen=True)
 class BaseDictCrown(Generic[T]):
     map: Mapping[str, T]
 
 
-@dataclass
+@dataclass(frozen=True)
 class BaseListCrown(Generic[T]):
     map: Sequence[T]
 
 
-@dataclass
+@dataclass(frozen=True)
 class BaseNoneCrown:
     pass
 
 
-@dataclass
+@dataclass(frozen=True)
 class BaseFieldCrown:
     id: str
 
@@ -65,22 +66,25 @@ class BaseFieldCrown:
 ListExtraPolicy = Union[ExtraSkip, ExtraForbid]
 
 
-@dataclass
+@dataclass(frozen=True)
 class InpDictCrown(BaseDictCrown["InpCrown"]):
     extra_policy: DictExtraPolicy
 
+    def __hash__(self):
+        return hash(MappingHashWrapper(self.map))
+
 
-@dataclass
+@dataclass(frozen=True)
 class InpListCrown(BaseListCrown["InpCrown"]):
     extra_policy: ListExtraPolicy
 
 
-@dataclass
+@dataclass(frozen=True)
 class InpNoneCrown(BaseNoneCrown):
     pass
 
 
-@dataclass
+@dataclass(frozen=True)
 class InpFieldCrown(BaseFieldCrown):
     pass
 
@@ -96,9 +100,9 @@ class InpFieldCrown(BaseFieldCrown):
 Sieve = Callable[[Any, Any], bool]
 
 
-@dataclass
+@dataclass(frozen=True)
 class OutDictCrown(BaseDictCrown["OutCrown"]):
-    sieves: Dict[str, Sieve]
+    sieves: dict[str, Sieve]
 
     def _validate(self):
         wild_sieves = self.sieves.keys() - self.map.keys()
@@ -110,8 +114,11 @@ def _validate(self):
     def __post_init__(self):
         self._validate()
 
+    def __hash__(self):
+        return hash((MappingHashWrapper(self.map), MappingHashWrapper(self.sieves)))
+
 
-@dataclass
+@dataclass(frozen=True)
 class OutListCrown(BaseListCrown["OutCrown"]):
     pass
 
@@ -119,12 +126,12 @@ class OutListCrown(BaseListCrown["OutCrown"]):
 Placeholder = Union[DefaultValue, DefaultFactory]
 
 
-@dataclass
+@dataclass(frozen=True)
 class OutNoneCrown(BaseNoneCrown):
     placeholder: Placeholder
 
 
-@dataclass
+@dataclass(frozen=True)
 class OutFieldCrown(BaseFieldCrown):
     pass
 
diff --git a/src/adaptix/_internal/morphing/model/dumper_gen.py b/src/adaptix/_internal/morphing/model/dumper_gen.py
index 698eea94..0e961d6a 100644
--- a/src/adaptix/_internal/morphing/model/dumper_gen.py
+++ b/src/adaptix/_internal/morphing/model/dumper_gen.py
@@ -1,6 +1,8 @@
 import contextlib
+from collections.abc import Mapping
+from dataclasses import replace
 from string import Template
-from typing import Dict, Mapping, NamedTuple, Tuple
+from typing import Any, Callable, NamedTuple
 
 from ...code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
 from ...code_tools.code_builder import CodeBuilder
@@ -19,6 +21,9 @@
 )
 from ...special_cases_optimization import as_is_stub, get_default_clause
 from ...struct_trail import append_trail, extend_trail, render_trail_as_note
+from ...utils import Omittable, Omitted
+from ..json_schema.definitions import JSONSchema
+from ..json_schema.schema_model import JSONSchemaType, JSONValue
 from .basic_gen import ModelDumperGen, get_skipped_fields
 from .crown_definitions import (
     CrownPath,
@@ -27,6 +32,7 @@
     ExtraTargets,
     OutCrown,
     OutDictCrown,
+    OutExtraMove,
     OutFieldCrown,
     OutListCrown,
     OutNoneCrown,
@@ -40,8 +46,8 @@ def __init__(self, builder: CodeBuilder, namespace: CascadeNamespace):
         self.builder = builder
         self.namespace = namespace
 
-        self.field_id_to_path: Dict[str, CrownPath] = {}
-        self.path_to_suffix: Dict[CrownPath, str] = {}
+        self.field_id_to_path: dict[str, CrownPath] = {}
+        self.path_to_suffix: dict[CrownPath, str] = {}
 
         self._last_path_idx = 0
         self._path: CrownPath = ()
@@ -111,10 +117,10 @@ def __init__(
             if isinstance(self._name_layout.extra_move, ExtraTargets)
             else ()
         )
-        self._id_to_field: Dict[str, OutputField] = {field.id: field for field in self._shape.fields}
+        self._id_to_field: dict[str, OutputField] = {field.id: field for field in self._shape.fields}
         self._model_identity = model_identity
 
-    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, closure_name: str) -> tuple[str, Mapping[str, object]]:
         body_builder = CodeBuilder()
 
         namespace = BuiltinCascadeNamespace()
@@ -648,3 +654,78 @@ def _gen_field_crown(self, state: GenState, crown: OutFieldCrown):
 
     def _gen_none_crown(self, state: GenState, crown: OutNoneCrown):
         pass
+
+
+class ModelOutputJSONSchemaGen:
+    def __init__(
+        self,
+        shape: OutputShape,
+        extra_move: OutExtraMove,
+        field_json_schema_getter: Callable[[OutputField], JSONSchema],
+        field_default_dumper: Callable[[OutputField], Omittable[JSONValue]],
+        placeholder_dumper: Callable[[Any], JSONValue],
+    ):
+        self._shape = shape
+        self._extra_move = extra_move
+        self._field_json_schema_getter = field_json_schema_getter
+        self._field_default_dumper = field_default_dumper
+        self._placeholder_dumper = placeholder_dumper
+
+    def _convert_dict_crown(self, crown: OutDictCrown) -> JSONSchema:
+        return JSONSchema(
+            type=JSONSchemaType.OBJECT,
+            required=[
+                key
+                for key, value in crown.map.items()
+                if self._is_required_crown(value)
+            ],
+            properties={
+                key: self.convert_crown(value)
+                for key, value in crown.map.items()
+            },
+            additional_properties=self._extra_move is not None,
+        )
+
+    def _convert_list_crown(self, crown: OutListCrown) -> JSONSchema:
+        items = [
+            self.convert_crown(sub_crown)
+            for sub_crown in crown.map
+        ]
+        return JSONSchema(
+            type=JSONSchemaType.ARRAY,
+            prefix_items=items,
+            max_items=len(items),
+            min_items=len(items),
+        )
+
+    def _convert_field_crown(self, crown: OutFieldCrown) -> JSONSchema:
+        field = self._shape.fields_dict[crown.id]
+        json_schema = self._field_json_schema_getter(field)
+        default = self._field_default_dumper(field)
+        if default != Omitted():
+            return replace(json_schema, default=default)
+        return json_schema
+
+    def _convert_none_crown(self, crown: OutNoneCrown) -> JSONSchema:
+        value = (
+            crown.placeholder.factory()
+            if isinstance(crown.placeholder, DefaultFactory) else
+            crown.placeholder.value
+        )
+        return JSONSchema(const=self._placeholder_dumper(value))
+
+    def _is_required_crown(self, crown: OutCrown) -> bool:
+        if isinstance(crown, OutFieldCrown):
+            return self._shape.fields_dict[crown.id].is_required
+        return True
+
+    def convert_crown(self, crown: OutCrown) -> JSONSchema:
+        if isinstance(crown, OutDictCrown):
+            return self._convert_dict_crown(crown)
+        if isinstance(crown, OutListCrown):
+            return self._convert_list_crown(crown)
+        if isinstance(crown, OutFieldCrown):
+            return self._convert_field_crown(crown)
+        if isinstance(crown, OutNoneCrown):
+            return self._convert_none_crown(crown)
+        raise TypeError
diff --git a/src/adaptix/_internal/morphing/model/dumper_provider.py b/src/adaptix/_internal/morphing/model/dumper_provider.py
index 7a105375..9b6b56f7 100644
--- a/src/adaptix/_internal/morphing/model/dumper_provider.py
+++ b/src/adaptix/_internal/morphing/model/dumper_provider.py
@@ -1,18 +1,24 @@
-from typing import Mapping
-
-from adaptix._internal.provider.fields import output_field_to_loc
+from collections.abc import Mapping, Sequence
+from functools import partial
+from typing import Any
 
 from ...code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ...code_tools.name_sanitizer import BuiltinNameSanitizer, NameSanitizer
 from ...common import Dumper
-from ...definitions import DebugTrail
-from ...model_tools.definitions import OutputShape
-from ...provider.essential import Mediator
-from ...provider.request_cls import DebugTrailRequest
+from ...definitions import DebugTrail, Direction
+from ...model_tools.definitions import DefaultFactory, DefaultValue, OutputField, OutputShape
+from ...provider.essential import CannotProvide, Mediator
+from ...provider.fields import output_field_to_loc
+from ...provider.located_request import LocatedRequest
 from ...provider.shape_provider import OutputShapeRequest, provide_generic_resolved_shape
-from ..provider_template import DumperProvider
-from ..request_cls import DumperRequest
+from ...utils import AlwaysEqualHashWrapper, Omittable, Omitted, OrderedMappingHashWrapper
+from ..json_schema.definitions import JSONSchema
+from ..json_schema.request_cls import JSONSchemaRequest
+from ..json_schema.schema_model import JSONValue
+from ..provider_template import DumperProvider, JSONSchemaProvider
+from ..request_cls import DebugTrailRequest, DumperRequest
 from .basic_gen import (
+    CodeGenHook,
     ModelDumperGen,
     compile_closure_with_globals_capturing,
     fetch_code_gen_hook,
@@ -20,28 +26,122 @@
     get_optional_fields_at_list_crown,
     get_wild_extra_targets,
 )
-from .crown_definitions import OutputNameLayout, OutputNameLayoutRequest
-from .dumper_gen import BuiltinModelDumperGen
+from .crown_definitions import OutExtraMove, OutputNameLayout, OutputNameLayoutRequest
+from .dumper_gen import BuiltinModelDumperGen, ModelOutputJSONSchemaGen
 
 
-class ModelDumperProvider(DumperProvider):
+class ModelDumperProvider(DumperProvider, JSONSchemaProvider):
     def __init__(self, *, name_sanitizer: NameSanitizer = BuiltinNameSanitizer()):
         self._name_sanitizer = name_sanitizer
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        dumper_gen = self._fetch_model_dumper_gen(mediator, request)
-        closure_name = self._get_closure_name(request)
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        shape = self._fetch_shape(mediator, request)
+        name_layout = self._fetch_name_layout(mediator, request, shape)
+        fields_dumpers = self._fetch_field_dumpers(mediator, request, shape)
+        return mediator.cached_call(
+            self._make_dumper,
+            shape=shape,
+            name_layout=name_layout,
+            fields_dumpers=OrderedMappingHashWrapper(fields_dumpers),
+            debug_trail=mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack)),
+            code_gen_hook=AlwaysEqualHashWrapper(fetch_code_gen_hook(mediator, request.loc_stack)),
+            model_identity=self._fetch_model_identity(mediator, request, shape, name_layout),
+            closure_name=self._get_closure_name(request),
+            file_name=self._get_file_name(request),
+        )
+
+    def _make_dumper(
+        self,
+        *,
+        shape: OutputShape,
+        name_layout: OutputNameLayout,
+        fields_dumpers: OrderedMappingHashWrapper[Mapping[str, Dumper]],
+        debug_trail: DebugTrail,
+        code_gen_hook: AlwaysEqualHashWrapper[CodeGenHook],
+        model_identity: str,
+        closure_name: str,
+        file_name: str,
+    ) -> Dumper:
+        self._validate_params(shape, name_layout)
+        dumper_gen = self._create_model_dumper_gen(
+            debug_trail=debug_trail,
+            shape=shape,
+            name_layout=name_layout,
+            fields_dumpers=fields_dumpers.mapping,
+            model_identity=model_identity,
+        )
         dumper_code, dumper_namespace = dumper_gen.produce_code(closure_name=closure_name)
         return compile_closure_with_globals_capturing(
             compiler=self._get_compiler(),
-            code_gen_hook=fetch_code_gen_hook(mediator, request.loc_stack),
+            code_gen_hook=code_gen_hook.value,
             namespace=dumper_namespace,
             closure_code=dumper_code,
             closure_name=closure_name,
-            file_name=self._get_file_name(request),
+            file_name=file_name,
+        )
+
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        if request.ctx.direction != Direction.OUTPUT:
+            raise CannotProvide
+
+        shape = self._fetch_shape(mediator, request)
+        name_layout = self._fetch_name_layout(mediator, request, shape)
+        self._validate_params(shape, name_layout)
+
+        schema_gen = self._get_schema_gen(mediator, request, shape, name_layout.extra_move)
+        return schema_gen.convert_crown(name_layout.crown)
+
+    def _get_schema_gen(
+        self,
+        mediator: Mediator,
+        request: JSONSchemaRequest,
+        shape: OutputShape,
+        extra_move: OutExtraMove,
+    ) -> ModelOutputJSONSchemaGen:
+        return ModelOutputJSONSchemaGen(
+            shape=shape,
+            field_default_dumper=partial(self._dump_field_default, mediator, request),
+            field_json_schema_getter=partial(self._get_field_json_schema, mediator, request),
+            extra_move=extra_move,
+            placeholder_dumper=self._dump_placeholder,
+        )
+
+    def _dump_field_default(
+        self,
+        mediator: Mediator,
+        request: JSONSchemaRequest,
+        field: OutputField,
+    ) -> Omittable[JSONValue]:
+        if isinstance(field.default, DefaultValue):
+            default_value = field.default.value
+        elif isinstance(field.default, DefaultFactory):
+            default_value = field.default.factory()
+        else:
+            return Omitted()
+
+        dumper = mediator.mandatory_provide(
+            DumperRequest(loc_stack=request.loc_stack.append_with(output_field_to_loc(field))),
         )
+        return dumper(default_value)
+
+    def _dump_placeholder(self, data: Any) -> JSONValue:
+        if isinstance(data, Mapping):
+            return {str(self._dump_placeholder(key)): self._dump_placeholder(value) for key, value in data.items()}
+        if isinstance(data, Sequence):
+            return [self._dump_placeholder(element) for element in data]
+        if isinstance(data, (str, int, float, bool)) or data is None:
+            return data
+        raise TypeError(f"Can not dump placeholder {data}")
+
+    def _get_field_json_schema(
+        self,
+        mediator: Mediator,
+        request: JSONSchemaRequest,
+        field: OutputField,
+    ) -> JSONSchema:
+        return mediator.mandatory_provide(request.append_loc(output_field_to_loc(field)))
 
-    def _fetch_model_dumper_gen(self, mediator: Mediator, request: DumperRequest) -> ModelDumperGen:
+    def _fetch_model_dumper_gen(self, mediator: Mediator, request: LocatedRequest) -> ModelDumperGen:
         shape = self._fetch_shape(mediator, request)
         name_layout = self._fetch_name_layout(mediator, request, shape)
         self._validate_params(shape, name_layout)
@@ -59,7 +159,7 @@ def _fetch_model_dumper_gen(self, mediator: Mediator, request: DumperRequest) ->
     def _fetch_model_identity(
         self,
         mediator: Mediator,
-        request: DumperRequest,
+        request: LocatedRequest,
         shape: OutputShape,
         name_layout: OutputNameLayout,
     ) -> str:
@@ -103,10 +203,10 @@ def _get_closure_name(self, request: DumperRequest) -> str:
     def _get_compiler(self) -> ClosureCompiler:
         return BasicClosureCompiler()
 
-    def _fetch_shape(self, mediator: Mediator, request: DumperRequest) -> OutputShape:
+    def _fetch_shape(self, mediator: Mediator, request: LocatedRequest) -> OutputShape:
         return provide_generic_resolved_shape(mediator, OutputShapeRequest(loc_stack=request.loc_stack))
 
-    def _fetch_name_layout(self, mediator: Mediator, request: DumperRequest, shape: OutputShape) -> OutputNameLayout:
+    def _fetch_name_layout(self, mediator: Mediator, request: LocatedRequest, shape: OutputShape) -> OutputNameLayout:
         return mediator.delegating_provide(
             OutputNameLayoutRequest(
                 loc_stack=request.loc_stack,
@@ -117,12 +217,12 @@ def _fetch_name_layout(self, mediator: Mediator, request: DumperRequest, shape:
     def _fetch_field_dumpers(
         self,
         mediator: Mediator,
-        request: DumperRequest,
+        request: LocatedRequest,
         shape: OutputShape,
     ) -> Mapping[str, Dumper]:
         dumpers = mediator.mandatory_provide_by_iterable(
             [
-                DumperRequest(loc_stack=request.loc_stack.append_with(output_field_to_loc(field)))
+                request.append_loc(output_field_to_loc(field))
                 for field in shape.fields
             ],
             lambda: "Cannot create dumper for model. Dumpers for some fields cannot be created",
diff --git a/src/adaptix/_internal/morphing/model/loader_gen.py b/src/adaptix/_internal/morphing/model/loader_gen.py
index 411a3132..18c7941f 100644
--- a/src/adaptix/_internal/morphing/model/loader_gen.py
+++ b/src/adaptix/_internal/morphing/model/loader_gen.py
@@ -1,7 +1,8 @@
 import collections.abc
 import contextlib
-from dataclasses import dataclass
-from typing import AbstractSet, Dict, List, Mapping, Optional, Set, Tuple
+from collections.abc import Mapping, Set
+from dataclasses import dataclass, replace
+from typing import Callable, Optional
 
 from ...code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
 from ...code_tools.code_builder import CodeBuilder
@@ -12,6 +13,9 @@
 from ...model_tools.definitions import DefaultFactory, DefaultValue, InputField, InputShape, Param, ParamKind
 from ...special_cases_optimization import as_is_stub
 from ...struct_trail import append_trail, extend_trail, render_trail_as_note
+from ...utils import Omittable, Omitted
+from ..json_schema.definitions import JSONSchema
+from ..json_schema.schema_model import JSONSchemaType, JSONValue
 from ..load_error import (
     AggregateLoadError,
     ExcludedTypeLoadError,
@@ -97,13 +101,13 @@ def emit_error(self, error_expr: str) -> str:
 
 
 class GenState(Namer):
-    path_to_suffix: Dict[CrownPath, str]
+    path_to_suffix: dict[CrownPath, str]
 
     def __init__(
         self,
         builder: CodeBuilder,
         namespace: CascadeNamespace,
-        name_to_field: Dict[str, InputField],
+        name_to_field: dict[str, InputField],
         debug_trail: DebugTrail,
         root_crown: InpCrown,
     ):
@@ -111,13 +115,13 @@ def __init__(
         self.namespace = namespace
         self._name_to_field = name_to_field
 
-        self.field_id_to_path: Dict[str, CrownPath] = {}
+        self.field_id_to_path: dict[str, CrownPath] = {}
 
         self._last_path_idx = 0
         self._parent_path: Optional[CrownPath] = None
-        self._crown_stack: List[InpCrown] = [root_crown]
+        self._crown_stack: list[InpCrown] = [root_crown]
 
-        self.type_checked_type_paths: Set[CrownPath] = set()
+        self.type_checked_type_paths: set[CrownPath] = set()
         super().__init__(debug_trail=debug_trail, path_to_suffix={}, path=())
 
     @property
@@ -181,7 +185,7 @@ def __init__(
         debug_trail: DebugTrail,
         strict_coercion: bool,
         field_loaders: Mapping[str, Loader],
-        skipped_fields: AbstractSet[str],
+        skipped_fields: Set[str],
         model_identity: str,
         props: ModelLoaderProps,
     ):
@@ -189,10 +193,10 @@ def __init__(
         self._name_layout = name_layout
         self._debug_trail = debug_trail
         self._strict_coercion = strict_coercion
-        self._id_to_field: Dict[str, InputField] = {
+        self._id_to_field: dict[str, InputField] = {
             field.id: field for field in self._shape.fields
         }
-        self._field_id_to_param: Dict[str, Param] = {
+        self._field_id_to_param: dict[str, Param] = {
             param.field_id: param for param in self._shape.params
         }
         self._field_loaders = field_loaders
@@ -229,7 +233,7 @@ def _is_packed_field(self, field: InputField) -> bool:
             return False
         return field.is_optional and not self._is_extra_target(field)
 
-    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, closure_name: str) -> tuple[str, Mapping[str, object]]:
         namespace = BuiltinCascadeNamespace()
         state = self._create_state(namespace)
 
@@ -243,7 +247,7 @@ def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
             TypeLoadError, ExcludedTypeLoadError,
             LoadError, AggregateLoadError,
         ):
-            state.namespace.add_constant(named_value.__name__, named_value)  # type: ignore[attr-defined]
+            state.namespace.add_constant(named_value.__name__, named_value)
 
         state.namespace.add_constant("CompatExceptionGroup", CompatExceptionGroup)
         state.namespace.add_constant("CollectionsMapping", collections.abc.Mapping)
@@ -261,7 +265,7 @@ def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
         if not self._gen_root_crown_dispatch(state, self._name_layout.crown):
             raise TypeError
 
-        self._gen_extra_targets_assigment(state)
+        self._gen_extra_targets_assignment(state)
 
         if self._debug_trail == DebugTrail.ALL:
             state.builder(
@@ -487,7 +491,7 @@ def _maybe_wrap_with_type_load_error_catching(self, state: GenState):
         )
         state.builder.empty_line()
 
-    def _get_dict_crown_required_keys(self, crown: InpDictCrown) -> Set[str]:
+    def _get_dict_crown_required_keys(self, crown: InpDictCrown) -> set[str]:
         return {
             key for key, value in crown.map.items()
             if not (isinstance(value, InpFieldCrown) and self._id_to_field[value.id].is_optional)
@@ -603,7 +607,7 @@ def _gen_field_crown(self, state: GenState, crown: InpFieldCrown):
                 assign_to=state.v_raw_field(field),
             )
             with state.builder("else:"):
-                self._gen_field_assigment(
+                self._gen_field_assignment(
                     assign_to=state.v_field(field),
                     field_id=field.id,
                     loader_arg=state.v_raw_field(field),
@@ -625,7 +629,7 @@ def _gen_field_crown(self, state: GenState, crown: InpFieldCrown):
                     on_lookup_error=on_lookup_error,
                 )
                 with state.builder("else:"):
-                    self._gen_field_assigment(
+                    self._gen_field_assignment(
                         assign_to=assign_to,
                         field_id=field.id,
                         loader_arg=state.v_raw_field(field),
@@ -651,7 +655,7 @@ def _gen_optional_field_extraction_from_mapping(
     ):
         if state.parent_path in state.type_checked_type_paths:
             with state.builder(f"if {state.path[-1]!r} in {state.parent.v_data}:"):
-                self._gen_field_assigment(
+                self._gen_field_assignment(
                     assign_to=assign_to,
                     field_id=field.id,
                     loader_arg=f"{state.parent.v_data}[{state.path[-1]!r}]",
@@ -690,7 +694,7 @@ def _gen_optional_field_extraction_from_mapping(
                     else:
                     """,
                 ):
-                    self._gen_field_assigment(
+                    self._gen_field_assignment(
                         assign_to=assign_to,
                         field_id=field.id,
                         loader_arg="value",
@@ -712,14 +716,14 @@ def _gen_optional_field_extraction_from_mapping(
                         else:
                         """,
                     ):
-                        self._gen_field_assigment(
+                        self._gen_field_assignment(
                             assign_to=assign_to,
                             field_id=field.id,
                             loader_arg="value",
                             state=state,
                         )
 
-    def _gen_field_assigment(
+    def _gen_field_assignment(
         self,
         assign_to: str,
         field_id: str,
@@ -746,7 +750,7 @@ def _gen_field_assigment(
                 f"{assign_to} = {processing_expr}",
             )
 
-    def _gen_extra_targets_assigment(self, state: GenState):
+    def _gen_extra_targets_assignment(self, state: GenState):
         # Saturate extra targets with data.
         # If extra data is not collected, loader of the required field will get empty dict
         extra_move = self._name_layout.extra_move
@@ -758,7 +762,7 @@ def _gen_extra_targets_assigment(self, state: GenState):
             for target in extra_move.fields:
                 field = self._id_to_field[target]
 
-                self._gen_field_assigment(
+                self._gen_field_assignment(
                     assign_to=state.v_field(field),
                     field_id=target,
                     loader_arg=state.v_extra,
@@ -768,7 +772,7 @@ def _gen_extra_targets_assigment(self, state: GenState):
             for target in extra_move.fields:
                 field = self._id_to_field[target]
                 if field.is_required:
-                    self._gen_field_assigment(
+                    self._gen_field_assignment(
                         assign_to=state.v_field(field),
                         field_id=target,
                         loader_arg="{}",
@@ -779,3 +783,69 @@ def _gen_extra_targets_assigment(self, state: GenState):
 
     def _gen_none_crown(self, state: GenState, crown: InpNoneCrown):
         pass
+
+
+class ModelInputJSONSchemaGen:
+    def __init__(
+        self,
+        shape: InputShape,
+        field_json_schema_getter: Callable[[InputField], JSONSchema],
+        field_default_dumper: Callable[[InputField], Omittable[JSONValue]],
+    ):
+        self._shape = shape
+        self._field_json_schema_getter = field_json_schema_getter
+        self._field_default_dumper = field_default_dumper
+
+    def _convert_dict_crown(self, crown: InpDictCrown) -> JSONSchema:
+        return JSONSchema(
+            type=JSONSchemaType.OBJECT,
+            required=[
+                key
+                for key, value in crown.map.items()
+                if self._is_required_crown(value)
+            ],
+            properties={
+                key: self.convert_crown(value)
+                for key, value in crown.map.items()
+            },
+            additional_properties=crown.extra_policy != ExtraForbid(),
+        )
+
+    def _convert_list_crown(self, crown: InpListCrown) -> JSONSchema:
+        items = [
+            self.convert_crown(sub_crown)
+            for sub_crown in crown.map
+        ]
+        return JSONSchema(
+            type=JSONSchemaType.ARRAY,
+            prefix_items=items,
+            max_items=len(items) if crown.extra_policy != ExtraForbid() else Omitted(),
+            min_items=len(items),
+        )
+
+    def _convert_field_crown(self, crown: InpFieldCrown) -> JSONSchema:
+        field = self._shape.fields_dict[crown.id]
+        json_schema = self._field_json_schema_getter(field)
+        default = self._field_default_dumper(field)
+        if default != Omitted():
+            return replace(json_schema, default=default)
+        return json_schema
+
+    def _convert_none_crown(self, crown: InpNoneCrown) -> JSONSchema:
+        return JSONSchema()
+
+    def _is_required_crown(self, crown: InpCrown) -> bool:
+        if isinstance(crown, InpFieldCrown):
+            return self._shape.fields_dict[crown.id].is_required
+        return isinstance(crown, InpNoneCrown)
+
+    def convert_crown(self, crown: InpCrown) -> JSONSchema:
+        if isinstance(crown, InpDictCrown):
+            return self._convert_dict_crown(crown)
+        if isinstance(crown, InpListCrown):
+            return self._convert_list_crown(crown)
+        if isinstance(crown, InpFieldCrown):
+            return self._convert_field_crown(crown)
+        if isinstance(crown, InpNoneCrown):
+            return self._convert_none_crown(crown)
+        raise TypeError
diff --git a/src/adaptix/_internal/morphing/model/loader_provider.py b/src/adaptix/_internal/morphing/model/loader_provider.py
index fb20240b..e20d7c5b 100644
--- a/src/adaptix/_internal/morphing/model/loader_provider.py
+++ b/src/adaptix/_internal/morphing/model/loader_provider.py
@@ -1,19 +1,24 @@
-from typing import AbstractSet, Mapping
-
-from adaptix._internal.provider.fields import input_field_to_loc
+from collections.abc import Mapping, Set
+from functools import partial
 
 from ...code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ...code_tools.name_sanitizer import BuiltinNameSanitizer, NameSanitizer
 from ...common import Loader
-from ...definitions import DebugTrail
-from ...model_tools.definitions import InputShape
-from ...provider.essential import Mediator
-from ...provider.request_cls import DebugTrailRequest, StrictCoercionRequest
+from ...definitions import DebugTrail, Direction
+from ...model_tools.definitions import DefaultFactory, DefaultValue, InputField, InputShape
+from ...provider.essential import CannotProvide, Mediator
+from ...provider.fields import input_field_to_loc
+from ...provider.located_request import LocatedRequest
 from ...provider.shape_provider import InputShapeRequest, provide_generic_resolved_shape
-from ..model.loader_gen import BuiltinModelLoaderGen, ModelLoaderProps
-from ..provider_template import LoaderProvider
-from ..request_cls import LoaderRequest
+from ...utils import AlwaysEqualHashWrapper, Omittable, Omitted, OrderedMappingHashWrapper
+from ..json_schema.definitions import JSONSchema
+from ..json_schema.request_cls import JSONSchemaRequest
+from ..json_schema.schema_model import JSONValue
+from ..model.loader_gen import BuiltinModelLoaderGen, ModelInputJSONSchemaGen, ModelLoaderProps
+from ..provider_template import JSONSchemaProvider, LoaderProvider
+from ..request_cls import DebugTrailRequest, DumperRequest, LoaderRequest, StrictCoercionRequest
 from .basic_gen import (
+    CodeGenHook,
     ModelLoaderGen,
     compile_closure_with_globals_capturing,
     fetch_code_gen_hook,
@@ -26,7 +31,7 @@
 from .crown_definitions import InputNameLayout, InputNameLayoutRequest
 
 
-class ModelLoaderProvider(LoaderProvider):
+class ModelLoaderProvider(LoaderProvider, JSONSchemaProvider):
     def __init__(
         self,
         *,
@@ -36,19 +41,106 @@ def __init__(
         self._name_sanitizer = name_sanitizer
         self._props = props
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        loader_gen = self._fetch_model_loader_gen(mediator, request)
-        closure_name = self._get_closure_name(request)
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        shape = self._fetch_shape(mediator, request)
+        name_layout = self._fetch_name_layout(mediator, request, shape)
+        field_loaders = self._fetch_field_loaders(mediator, request, shape)
+        return mediator.cached_call(
+            self._make_loader,
+            shape=shape,
+            name_layout=name_layout,
+            field_loaders=OrderedMappingHashWrapper(field_loaders),
+            strict_coercion=mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack)),
+            debug_trail=mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack)),
+            code_gen_hook=AlwaysEqualHashWrapper(fetch_code_gen_hook(mediator, request.loc_stack)),
+            model_identity=self._fetch_model_identity(mediator, request, shape, name_layout),
+            closure_name=self._get_closure_name(request),
+            file_name=self._get_file_name(request),
+        )
+
+    def _make_loader(
+        self,
+        *,
+        shape: InputShape,
+        name_layout: InputNameLayout,
+        field_loaders: OrderedMappingHashWrapper[Mapping[str, Loader]],
+        strict_coercion: bool,
+        debug_trail: DebugTrail,
+        code_gen_hook: AlwaysEqualHashWrapper[CodeGenHook],
+        model_identity: str,
+        closure_name: str,
+        file_name: str,
+    ) -> Loader:
+        skipped_fields = get_skipped_fields(shape, name_layout)
+        self._validate_params(shape, name_layout, skipped_fields)
+        loader_gen = self._create_model_loader_gen(
+            debug_trail=debug_trail,
+            strict_coercion=strict_coercion,
+            shape=shape,
+            name_layout=name_layout,
+            field_loaders=field_loaders.mapping,
+            skipped_fields=skipped_fields,
+            model_identity=model_identity,
+        )
         loader_code, loader_namespace = loader_gen.produce_code(closure_name=closure_name)
         return compile_closure_with_globals_capturing(
             compiler=self._get_compiler(),
-            code_gen_hook=fetch_code_gen_hook(mediator, request.loc_stack),
+            code_gen_hook=code_gen_hook.value,
             namespace=loader_namespace,
             closure_code=loader_code,
             closure_name=closure_name,
-            file_name=self._get_file_name(request),
+            file_name=file_name,
         )
 
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        if request.ctx.direction != Direction.INPUT:
+            raise CannotProvide
+
+        shape = self._fetch_shape(mediator, request)
+        name_layout = self._fetch_name_layout(mediator, request, shape)
+        skipped_fields = get_skipped_fields(shape, name_layout)
+        self._validate_params(shape, name_layout, skipped_fields)
+        schema_gen = self._get_schema_gen(mediator, request, shape)
+        return schema_gen.convert_crown(name_layout.crown)
+
+    def _get_schema_gen(
+        self,
+        mediator: Mediator,
+        request: JSONSchemaRequest,
+        shape: InputShape,
+    ) -> ModelInputJSONSchemaGen:
+        return ModelInputJSONSchemaGen(
+            shape=shape,
+            field_default_dumper=partial(self._dump_field_default, mediator, request),
+            field_json_schema_getter=partial(self._get_field_json_schema, mediator, request),
+        )
+
+    def _dump_field_default(
+        self,
+        mediator: Mediator,
+        request: JSONSchemaRequest,
+        field: InputField,
+    ) -> Omittable[JSONValue]:
+        if isinstance(field.default, DefaultValue):
+            default_value = field.default.value
+        elif isinstance(field.default, DefaultFactory):
+            default_value = field.default.factory()
+        else:
+            return Omitted()
+
+        dumper = mediator.mandatory_provide(
+            DumperRequest(loc_stack=request.loc_stack.append_with(input_field_to_loc(field))),
+        )
+        return dumper(default_value)
+
+    def _get_field_json_schema(
+        self,
+        mediator: Mediator,
+        request: JSONSchemaRequest,
+        field: InputField,
+    ) -> JSONSchema:
+        return mediator.mandatory_provide(request.append_loc(input_field_to_loc(field)))
+
     def _fetch_model_loader_gen(self, mediator: Mediator, request: LoaderRequest) -> ModelLoaderGen:
         shape = self._fetch_shape(mediator, request)
         name_layout = self._fetch_name_layout(mediator, request, shape)
@@ -85,7 +177,7 @@ def _create_model_loader_gen(
         shape: InputShape,
         name_layout: InputNameLayout,
         field_loaders: Mapping[str, Loader],
-        skipped_fields: AbstractSet[str],
+        skipped_fields: Set[str],
         model_identity: str,
     ) -> ModelLoaderGen:
         return BuiltinModelLoaderGen(
@@ -121,10 +213,10 @@ def _get_closure_name(self, request: LoaderRequest) -> str:
     def _get_compiler(self) -> ClosureCompiler:
         return BasicClosureCompiler()
 
-    def _fetch_shape(self, mediator: Mediator, request: LoaderRequest) -> InputShape:
+    def _fetch_shape(self, mediator: Mediator, request: LocatedRequest) -> InputShape:
         return provide_generic_resolved_shape(mediator, InputShapeRequest(loc_stack=request.loc_stack))
 
-    def _fetch_name_layout(self, mediator: Mediator, request: LoaderRequest, shape: InputShape) -> InputNameLayout:
+    def _fetch_name_layout(self, mediator: Mediator, request: LocatedRequest, shape: InputShape) -> InputNameLayout:
         return mediator.mandatory_provide(
             InputNameLayoutRequest(
                 loc_stack=request.loc_stack,
@@ -141,7 +233,7 @@ def _fetch_field_loaders(
     ) -> Mapping[str, Loader]:
         loaders = mediator.mandatory_provide_by_iterable(
             [
-                LoaderRequest(loc_stack=request.loc_stack.append_with(input_field_to_loc(field)))
+                request.append_loc(input_field_to_loc(field))
                 for field in shape.fields
             ],
             lambda: "Cannot create loader for model. Loaders for some fields cannot be created",
@@ -152,7 +244,7 @@ def _validate_params(
         self,
         shape: InputShape,
         name_layout: InputNameLayout,
-        skipped_fields: AbstractSet[str],
+        skipped_fields: Set[str],
     ) -> None:
         skipped_required_fields = [
             field.id
@@ -203,5 +295,5 @@ def __init__(
         super().__init__(name_sanitizer=name_sanitizer, props=props)
         self._shape = shape
 
-    def _fetch_shape(self, mediator: Mediator, request: LoaderRequest) -> InputShape:
+    def _fetch_shape(self, mediator: Mediator, request: LocatedRequest) -> InputShape:
         return self._shape
diff --git a/src/adaptix/_internal/morphing/model/request_filtering.py b/src/adaptix/_internal/morphing/model/request_filtering.py
index f6a897ed..af0f4d35 100644
--- a/src/adaptix/_internal/morphing/model/request_filtering.py
+++ b/src/adaptix/_internal/morphing/model/request_filtering.py
@@ -1,6 +1,5 @@
 from ...provider.essential import CannotProvide
-from ...provider.loc_stack_filtering import DirectMediator, LocStackChecker
-from ...provider.request_cls import LocStack
+from ...provider.loc_stack_filtering import DirectMediator, LocStack, LocStackChecker
 from ...provider.shape_provider import InputShapeRequest, OutputShapeRequest
 
 
diff --git a/src/adaptix/_internal/morphing/name_layout/base.py b/src/adaptix/_internal/morphing/name_layout/base.py
index 194d4a50..f3d49216 100644
--- a/src/adaptix/_internal/morphing/name_layout/base.py
+++ b/src/adaptix/_internal/morphing/name_layout/base.py
@@ -1,5 +1,6 @@
 from abc import ABC, abstractmethod
-from typing import Iterable, Mapping, TypeVar, Union
+from collections.abc import Iterable, Mapping
+from typing import TypeVar, Union
 
 from ...common import VarTuple
 from ...provider.essential import Mediator
diff --git a/src/adaptix/_internal/morphing/name_layout/component.py b/src/adaptix/_internal/morphing/name_layout/component.py
index 0a89b285..2172387d 100644
--- a/src/adaptix/_internal/morphing/name_layout/component.py
+++ b/src/adaptix/_internal/morphing/name_layout/component.py
@@ -1,6 +1,7 @@
 from collections import defaultdict
+from collections.abc import Iterable, Mapping, Sequence
 from dataclasses import dataclass
-from typing import Callable, DefaultDict, Dict, Iterable, List, Mapping, Optional, Sequence, Set, Tuple, TypeVar, Union
+from typing import Callable, Optional, TypeVar, Union
 
 from ...common import VarTuple
 from ...model_tools.definitions import (
@@ -17,8 +18,8 @@
 from ...provider.essential import CannotProvide, Mediator, Provider
 from ...provider.fields import field_to_loc
 from ...provider.loc_stack_filtering import LocStackChecker
+from ...provider.located_request import LocatedRequest
 from ...provider.overlay_schema import Overlay, Schema, provide_schema
-from ...provider.request_cls import LocatedRequest
 from ...retort.operating_retort import OperatingRetort
 from ...special_cases_optimization import with_default_clause
 from ...utils import Omittable, get_prefix_groups
@@ -89,7 +90,7 @@ def _merge_map(self, old: VarTuple[Provider], new: VarTuple[Provider]) -> VarTup
 LeafCr = TypeVar("LeafCr", bound=LeafBaseCrown)
 FieldCr = TypeVar("FieldCr", bound=BaseFieldCrown)
 F = TypeVar("F", bound=BaseField)
-FieldAndPath = Tuple[F, Optional[KeyPath]]
+FieldAndPath = tuple[F, Optional[KeyPath]]
 
 
 def apply_lsc(
@@ -102,6 +103,11 @@ def apply_lsc(
     return loc_stack_checker.check_loc_stack(mediator, loc_stack)
 
 
+class NameMappingRetort(OperatingRetort):
+    def provide_name_mapping(self, request: NameMappingRequest) -> Optional[KeyPath]:
+        return self._provide_from_recipe(request)
+
+
 class BuiltinStructureMaker(StructureMaker):
     def _generate_key(self, schema: StructureSchema, shape: BaseShape, field: BaseField) -> Key:
         if schema.as_list:
@@ -114,8 +120,8 @@ def _generate_key(self, schema: StructureSchema, shape: BaseShape, field: BaseFi
             name = convert_snake_style(name, schema.name_style)
         return name
 
-    def _create_map_provider(self, schema: StructureSchema) -> Provider:
-        return OperatingRetort(recipe=schema.map)
+    def _create_name_mapping_retort(self, schema: StructureSchema) -> NameMappingRetort:
+        return NameMappingRetort(recipe=schema.map)
 
     def _map_fields(
         self,
@@ -125,15 +131,14 @@ def _map_fields(
         extra_move: Union[InpExtraMove, OutExtraMove],
     ) -> Iterable[FieldAndPath]:
         extra_targets = extra_move.fields if isinstance(extra_move, ExtraTargets) else ()
-        map_provider = self._create_map_provider(schema)
+        retort = self._create_name_mapping_retort(schema)
         for field in request.shape.fields:
             if field.id in extra_targets:
                 continue
 
             generated_key = self._generate_key(schema, request.shape, field)
             try:
-                path = map_provider.apply_provider(
-                    mediator,
+                path = retort.provide_name_mapping(
                     NameMappingRequest(
                         shape=request.shape,
                         field=field,
@@ -159,7 +164,7 @@ def _validate_structure(
         request: LocatedRequest,
         fields_to_paths: Iterable[FieldAndPath],
     ) -> None:
-        paths_to_fields: DefaultDict[KeyPath, List[AnyField]] = defaultdict(list)
+        paths_to_fields: defaultdict[KeyPath, list[AnyField]] = defaultdict(list)
         for field, path in fields_to_paths:
             if path is not None:
                 paths_to_fields[path].append(field)
@@ -210,8 +215,8 @@ def _validate_structure(
                 is_demonstrative=True,
             )
 
-    def _iterate_sub_paths(self, paths: Iterable[KeyPath]) -> Iterable[Tuple[KeyPath, Key]]:
-        yielded: Set[Tuple[KeyPath, Key]] = set()
+    def _iterate_sub_paths(self, paths: Iterable[KeyPath]) -> Iterable[tuple[KeyPath, Key]]:
+        yielded: set[tuple[KeyPath, Key]] = set()
         for path in paths:
             for i in range(len(path) - 1, -1, -1):
                 result = path[:i], path[i]
@@ -221,9 +226,9 @@ def _iterate_sub_paths(self, paths: Iterable[KeyPath]) -> Iterable[Tuple[KeyPath
                 yielded.add(result)
                 yield result
 
-    def _get_paths_to_list(self, request: LocatedRequest, paths: Iterable[KeyPath]) -> Mapping[KeyPath, Set[int]]:
-        paths_to_lists: DefaultDict[KeyPath, Set[int]] = defaultdict(set)
-        paths_to_dicts: Set[KeyPath] = set()
+    def _get_paths_to_list(self, request: LocatedRequest, paths: Iterable[KeyPath]) -> Mapping[KeyPath, set[int]]:
+        paths_to_lists: defaultdict[KeyPath, set[int]] = defaultdict(set)
+        paths_to_dicts: set[KeyPath] = set()
         for sub_path, key in self._iterate_sub_paths(paths):
             if isinstance(key, int):
                 if sub_path in paths_to_dicts:
@@ -253,7 +258,7 @@ def _make_paths_to_leaves(
         field_crown: Callable[[str], FieldCr],
         gaps_filler: Callable[[KeyPath], LeafCr],
     ) -> PathsTo[Union[FieldCr, LeafCr]]:
-        paths_to_leaves: Dict[KeyPath, Union[FieldCr, LeafCr]] = {
+        paths_to_leaves: dict[KeyPath, Union[FieldCr, LeafCr]] = {
             path: field_crown(field.id)
             for field, path in fields_to_paths
             if path is not None
@@ -281,7 +286,7 @@ def make_inp_structure(
         extra_move: InpExtraMove,
     ) -> PathsTo[LeafInpCrown]:
         schema = provide_schema(StructureOverlay, mediator, request.loc_stack)
-        fields_to_paths: List[FieldAndPath[InputField]] = list(
+        fields_to_paths: list[FieldAndPath[InputField]] = list(
             self._map_fields(mediator, request, schema, extra_move),
         )
         skipped_required_fields = [
@@ -306,7 +311,7 @@ def make_out_structure(
         extra_move: OutExtraMove,
     ) -> PathsTo[LeafOutCrown]:
         schema = provide_schema(StructureOverlay, mediator, request.loc_stack)
-        fields_to_paths: List[FieldAndPath[OutputField]] = list(
+        fields_to_paths: list[FieldAndPath[OutputField]] = list(
             self._map_fields(mediator, request, schema, extra_move),
         )
         paths_to_leaves = self._make_paths_to_leaves(request, fields_to_paths, OutFieldCrown, self._fill_output_gap)
@@ -362,8 +367,8 @@ def make_sieves(
         return result
 
 
-def _paths_to_branches(paths_to_leaves: PathsTo[LeafBaseCrown]) -> Iterable[Tuple[KeyPath, Key]]:
-    yielded_branch_path: Set[KeyPath] = set()
+def _paths_to_branches(paths_to_leaves: PathsTo[LeafBaseCrown]) -> Iterable[tuple[KeyPath, Key]]:
+    yielded_branch_path: set[KeyPath] = set()
     for path in paths_to_leaves:
         for i in range(len(path) - 1, -2, -1):
             sub_path = path[:i]
@@ -432,7 +437,7 @@ def make_extra_policies(
     ) -> PathsTo[DictExtraPolicy]:
         schema = provide_schema(ExtraMoveAndPoliciesOverlay, mediator, request.loc_stack)
         policy = self._get_extra_policy(schema)
-        path_to_extra_policy: Dict[KeyPath, DictExtraPolicy] = {
+        path_to_extra_policy: dict[KeyPath, DictExtraPolicy] = {
             (): policy,
         }
         for path, key in _paths_to_branches(paths_to_leaves):
diff --git a/src/adaptix/_internal/morphing/name_layout/crown_builder.py b/src/adaptix/_internal/morphing/name_layout/crown_builder.py
index e33692ba..397d6c59 100644
--- a/src/adaptix/_internal/morphing/name_layout/crown_builder.py
+++ b/src/adaptix/_internal/morphing/name_layout/crown_builder.py
@@ -1,8 +1,9 @@
 import math
 from abc import ABC, abstractmethod
+from collections.abc import Mapping, Sequence
 from dataclasses import dataclass
 from itertools import groupby
-from typing import Dict, Generic, Mapping, Sequence, TypeVar, Union, cast
+from typing import Generic, TypeVar, Union, cast
 
 from ..model.crown_definitions import (
     BaseDictCrown,
@@ -97,10 +98,10 @@ def _get_list_crown_map(
         ]
         if paths_with_leaves and len(grouped_paths) != cast(int, paths_with_leaves[-1].path[len(current_path)]) + 1:
             raise ValueError(f"Found gaps in list mapping at {current_path}")
-        return [
+        return tuple(
             self._build_crown(path_group, len(current_path) + 1)
             for path_group in grouped_paths
-        ]
+        )
 
     @abstractmethod
     def _make_list_crown(self, current_path: KeyPath, paths_with_leaves: PathedLeaves[LeafCr]) -> ListCr:
@@ -131,7 +132,7 @@ def __init__(self, path_to_sieves: PathsTo[Sieve], paths_to_leaves: PathsTo[Leaf
         super().__init__(paths_to_leaves)
 
     def _make_dict_crown(self, current_path: KeyPath, paths_with_leaves: PathedLeaves[LeafOutCrown]) -> OutDictCrown:
-        key_to_sieve: Dict[str, Sieve] = {}
+        key_to_sieve: dict[str, Sieve] = {}
         for leaf_with_path in paths_with_leaves:
             sieve = self.path_to_sieves.get(leaf_with_path.path[:len(current_path) + 1])
             if sieve is not None:
diff --git a/src/adaptix/_internal/morphing/name_layout/name_mapping.py b/src/adaptix/_internal/morphing/name_layout/name_mapping.py
index 0cf6d12f..9c76368a 100644
--- a/src/adaptix/_internal/morphing/name_layout/name_mapping.py
+++ b/src/adaptix/_internal/morphing/name_layout/name_mapping.py
@@ -1,15 +1,16 @@
 from __future__ import annotations
 
+from abc import ABC, abstractmethod
+from collections.abc import Iterable, Mapping
 from dataclasses import dataclass
-from typing import Callable, Iterable, Mapping, Optional, Tuple, Union
+from typing import Callable, Optional, Union
 
 from ...common import EllipsisType
 from ...model_tools.definitions import BaseField, BaseShape, OutputField, is_valid_field_id
 from ...provider.essential import CannotProvide, Mediator, Provider
-from ...provider.loc_stack_filtering import LocStackChecker, Pred
-from ...provider.provider_wrapper import ProviderWithLSC
-from ...provider.request_cls import LocatedRequest
-from ...provider.static_provider import StaticProvider, static_provision_action
+from ...provider.loc_stack_filtering import Pred
+from ...provider.located_request import LocatedRequest
+from ...provider.methods_provider import MethodsProvider, method_handler
 from .base import Key, KeyPath
 
 RawKey = Union[Key, EllipsisType]
@@ -20,8 +21,8 @@
     Iterable[
         Union[
             Mapping[str, MapResult],
-            Tuple[Pred, MapResult],
-            Tuple[Pred, Callable[[BaseShape, BaseField], MapResult]],
+            tuple[Pred, MapResult],
+            tuple[Pred, Callable[[BaseShape, BaseField], MapResult]],
             Provider,
         ]
     ],
@@ -45,7 +46,14 @@ def resolve_map_result(generated_key: Key, map_result: MapResult) -> Optional[Ke
     return tuple(generated_key if isinstance(key, EllipsisType) else key for key in map_result)
 
 
-class DictNameMappingProvider(StaticProvider):
+class NameMappingProvider(MethodsProvider, ABC):
+    @abstractmethod
+    @method_handler
+    def provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
+        ...
+
+
+class DictNameMappingProvider(NameMappingProvider):
     def __init__(self, name_map: Mapping[str, MapResult]):
         self._name_map = name_map
         self._validate()
@@ -58,8 +66,7 @@ def _validate(self) -> None:
                 f" Keys {invalid_keys!r} does not meet this condition.",
             )
 
-    @static_provision_action
-    def _provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
+    def provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
         try:
             map_result = self._name_map[request.field.id]
         except KeyError:
@@ -67,38 +74,25 @@ def _provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest)
         return resolve_map_result(request.generated_key, map_result)
 
 
-class ConstNameMappingProvider(StaticProvider, ProviderWithLSC):
-    def __init__(self, loc_stack_checker: LocStackChecker, result: MapResult):
-        self._loc_stack_checker = loc_stack_checker
+class ConstNameMappingProvider(NameMappingProvider):
+    def __init__(self, result: MapResult):
         self._result = result
 
-    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
-        return self._loc_stack_checker
-
-    @static_provision_action
-    def _provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
-        self._apply_loc_stack_checker(mediator, request)
+    def provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
         return resolve_map_result(request.generated_key, self._result)
 
 
-class FuncNameMappingProvider(StaticProvider, ProviderWithLSC):
-    def __init__(self, loc_stack_checker: LocStackChecker, func: Callable[[BaseShape, BaseField], MapResult]):
-        self._loc_stack_checker = loc_stack_checker
+class FuncNameMappingProvider(NameMappingProvider):
+    def __init__(self, func: Callable[[BaseShape, BaseField], MapResult]):
         self._func = func
 
-    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
-        return self._loc_stack_checker
-
-    @static_provision_action
-    def _provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
-        self._apply_loc_stack_checker(mediator, request)
+    def provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
         result = self._func(request.shape, request.field)
         return resolve_map_result(request.generated_key, result)
 
 
-class SkipPrivateFieldsNameMappingProvider(StaticProvider):
-    @static_provision_action
-    def _provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
+class SkipPrivateFieldsNameMappingProvider(NameMappingProvider):
+    def provide_name_mapping(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
         if not isinstance(request.field, OutputField):
             raise CannotProvide
         if request.field.id.startswith("_"):
diff --git a/src/adaptix/_internal/morphing/name_layout/provider.py b/src/adaptix/_internal/morphing/name_layout/provider.py
index 8ae15fd7..ef753a24 100644
--- a/src/adaptix/_internal/morphing/name_layout/provider.py
+++ b/src/adaptix/_internal/morphing/name_layout/provider.py
@@ -2,7 +2,7 @@
 
 from ...model_tools.definitions import InputShape, OutputShape
 from ...provider.essential import Mediator
-from ...provider.static_provider import StaticProvider, static_provision_action
+from ...provider.methods_provider import MethodsProvider, method_handler
 from ..model.crown_definitions import (
     BranchInpCrown,
     BranchOutCrown,
@@ -21,7 +21,7 @@
 T = TypeVar("T")
 
 
-class BuiltinNameLayoutProvider(StaticProvider):
+class BuiltinNameLayoutProvider(MethodsProvider):
     def __init__(
         self,
         structure_maker: StructureMaker,
@@ -34,7 +34,7 @@ def __init__(
         self._extra_policies_maker = extra_policies_maker
         self._extra_move_maker = extra_move_maker
 
-    @static_provision_action
+    @method_handler
     def _provide_input_name_layout(self, mediator: Mediator, request: InputNameLayoutRequest) -> InputNameLayout:
         extra_move = self._extra_move_maker.make_inp_extra_move(mediator, request)
         paths_to_leaves = self._structure_maker.make_inp_structure(mediator, request, extra_move)
@@ -74,7 +74,7 @@ def _create_empty_input_crown(
     ) -> BranchInpCrown:
         return InpCrownBuilder(extra_policies, {}).build_empty_crown(as_list=as_list)
 
-    @static_provision_action
+    @method_handler
     def _provide_output_name_layout(self, mediator: Mediator, request: OutputNameLayoutRequest) -> OutputNameLayout:
         extra_move = self._extra_move_maker.make_out_extra_move(mediator, request)
         paths_to_leaves = self._structure_maker.make_out_structure(mediator, request, extra_move)
diff --git a/src/adaptix/_internal/morphing/provider_template.py b/src/adaptix/_internal/morphing/provider_template.py
index 83d50a77..077c48da 100644
--- a/src/adaptix/_internal/morphing/provider_template.py
+++ b/src/adaptix/_internal/morphing/provider_template.py
@@ -1,39 +1,57 @@
 from abc import ABC, abstractmethod
+from collections.abc import Container
 from typing import final
 
 from ..common import Dumper, Loader, TypeHint
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import ExactOriginLSC
-from ..provider.provider_template import ProviderWithAttachableLSC
-from ..provider.static_provider import static_provision_action
+from ..provider.located_request import LocatedRequestMethodsProvider
+from ..provider.methods_provider import method_handler
 from ..type_tools import normalize_type
+from .json_schema.definitions import JSONSchema
+from .json_schema.request_cls import JSONSchemaRequest
+from .json_schema.schema_model import JSONSchemaDialect
 from .request_cls import DumperRequest, LoaderRequest
 
 
-class LoaderProvider(ProviderWithAttachableLSC, ABC):
-    @final
-    @static_provision_action
-    def _outer_provide_loader(self, mediator: Mediator, request: LoaderRequest):
-        self._apply_loc_stack_checker(mediator, request)
-        return self._provide_loader(mediator, request)
+class LoaderProvider(LocatedRequestMethodsProvider, ABC):
+    @method_handler
+    @abstractmethod
+    def provide_loader(self, mediator: Mediator[Loader], request: LoaderRequest) -> Loader:
+        ...
+
 
+class DumperProvider(LocatedRequestMethodsProvider, ABC):
+    @method_handler
     @abstractmethod
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_dumper(self, mediator: Mediator[Dumper], request: DumperRequest) -> Dumper:
         ...
 
 
-class DumperProvider(ProviderWithAttachableLSC, ABC):
+class JSONSchemaProvider(LocatedRequestMethodsProvider, ABC):
+    SUPPORTED_JSON_SCHEMA_DIALECTS: Container[str] = (JSONSchemaDialect.DRAFT_2020_12, )
+
     @final
-    @static_provision_action
-    def _outer_provide_dumper(self, mediator: Mediator, request: DumperRequest):
-        self._apply_loc_stack_checker(mediator, request)
-        return self._provide_dumper(mediator, request)
+    @method_handler
+    def provide_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        if request.ctx.dialect not in self.SUPPORTED_JSON_SCHEMA_DIALECTS:
+            raise CannotProvide(f"Dialect {request.ctx.dialect} is not supported for this type")
+        return self._generate_json_schema(mediator, request)
 
     @abstractmethod
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def _generate_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
         ...
 
 
+class MorphingProvider(
+    LoaderProvider,
+    DumperProvider,
+    JSONSchemaProvider,
+    ABC,
+):
+    pass
+
+
 class ABCProxy(LoaderProvider, DumperProvider):
     def __init__(self, abstract: TypeHint, impl: TypeHint, *, for_loader: bool = True, for_dumper: bool = True):
         self._abstract = normalize_type(abstract).origin
@@ -42,7 +60,7 @@ def __init__(self, abstract: TypeHint, impl: TypeHint, *, for_loader: bool = Tru
         self._for_loader = for_loader
         self._for_dumper = for_dumper
 
-    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+    def provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         if not self._for_loader:
             raise CannotProvide
 
@@ -53,7 +71,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             lambda x: f"Cannot create loader for union. Loader for {self._impl} cannot be created",
         )
 
-    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+    def provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         if not self._for_dumper:
             raise CannotProvide
 
diff --git a/src/adaptix/_internal/morphing/request_cls.py b/src/adaptix/_internal/morphing/request_cls.py
index 163d80bc..45427d41 100644
--- a/src/adaptix/_internal/morphing/request_cls.py
+++ b/src/adaptix/_internal/morphing/request_cls.py
@@ -1,7 +1,8 @@
 from dataclasses import dataclass
 
 from ..common import Dumper, Loader
-from ..provider.request_cls import LocatedRequest
+from ..definitions import DebugTrail
+from ..provider.located_request import LocatedRequest
 
 
 @dataclass(frozen=True)
@@ -12,3 +13,11 @@ class LoaderRequest(LocatedRequest[Loader]):
 @dataclass(frozen=True)
 class DumperRequest(LocatedRequest[Dumper]):
     pass
+
+
+class StrictCoercionRequest(LocatedRequest[bool]):
+    pass
+
+
+class DebugTrailRequest(LocatedRequest[DebugTrail]):
+    pass
diff --git a/src/adaptix/_internal/morphing/utils.py b/src/adaptix/_internal/morphing/utils.py
new file mode 100644
index 00000000..9f0e6ef5
--- /dev/null
+++ b/src/adaptix/_internal/morphing/utils.py
@@ -0,0 +1,10 @@
+from ..common import TypeHint
+from ..provider.essential import CannotProvide
+from ..type_tools import BaseNormType, normalize_type
+
+
+def try_normalize_type(tp: TypeHint) -> BaseNormType:
+    try:
+        return normalize_type(tp)
+    except ValueError:
+        raise CannotProvide(f"{tp} can not be normalized")
diff --git a/src/adaptix/_internal/provider/essential.py b/src/adaptix/_internal/provider/essential.py
index 955ca42f..7c6668f1 100644
--- a/src/adaptix/_internal/provider/essential.py
+++ b/src/adaptix/_internal/provider/essential.py
@@ -1,6 +1,8 @@
+import typing
 from abc import ABC, abstractmethod
+from collections.abc import Iterable, Sequence
 from dataclasses import dataclass
-from typing import Any, Callable, Generic, Iterable, Optional, Sequence, TypeVar, final
+from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, TypeVar, final
 
 from ..common import VarTuple
 from ..compat import CompatExceptionGroup
@@ -29,10 +31,12 @@ def __init__(
         *,
         is_terminal: bool = False,
         is_demonstrative: bool = False,
+        parent_notes_gen: Optional[Callable[[], Sequence[str]]] = None,
     ):
         self.message = message
         self.is_terminal = is_terminal
         self.is_demonstrative = is_demonstrative
+        self.parent_notes_gen = parent_notes_gen
 
     def __repr__(self):
         return (
@@ -50,10 +54,12 @@ def __init__(
         *,
         is_terminal: bool = False,
         is_demonstrative: bool = False,
+        parent_notes_gen: Optional[Callable[[], Sequence[str]]] = None,
     ):
         # Parameter `message` is saved by `__new__` of CompatExceptionGroup
         self.is_terminal = is_terminal
         self.is_demonstrative = is_demonstrative
+        self.parent_notes_gen = parent_notes_gen
 
     if not HAS_NATIVE_EXC_GROUP:
         def __new__(
@@ -106,10 +112,7 @@ def make(
         )
 
 
-V = TypeVar("V")
-
-
-class Mediator(ABC, Generic[V]):
+class DirectMediator(ABC):
     """Mediator is an object that gives provider access to other providers
     and that stores the state of the current search.
 
@@ -125,12 +128,6 @@ def provide(self, request: Request[T]) -> T:
         :raise CannotProvide: A provider able to process the request does not be found
         """
 
-    @abstractmethod
-    def provide_from_next(self) -> V:
-        """Forward current request to providers
-        that placed after current provider at the recipe.
-        """
-
     @final
     def delegating_provide(
         self,
@@ -212,13 +209,49 @@ def mandatory_apply_by_iterable(
     return results
 
 
+ResponseT = TypeVar("ResponseT")
+
+
+if TYPE_CHECKING:
+    P = typing.ParamSpec("P")
+
+
+class Mediator(DirectMediator, ABC, Generic[ResponseT]):
+    """Mediator is an object that gives provider access to other providers
+    and that stores the state of the current search.
+
+    Mediator is a proxy to providers of retort.
+    """
+
+    @abstractmethod
+    def provide_from_next(self) -> ResponseT:
+        """Forward current request to providers
+        that placed after current provider at the recipe.
+        """
+
+    if TYPE_CHECKING:
+        @abstractmethod
+        def cached_call(self, func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
+            ...
+    else:
+        @abstractmethod
+        def cached_call(self, func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
+            ...
+
+
+RequestT = TypeVar("RequestT", bound=Request)
+RequestHandler = Callable[[Mediator[ResponseT], RequestT], ResponseT]
+
+
+class RequestChecker(ABC, Generic[RequestT]):
+    @abstractmethod
+    def check_request(self, mediator: DirectMediator, request: RequestT, /) -> bool:
+        ...
+
+
 class Provider(ABC):
     """An object that can process Request instances"""
 
     @abstractmethod
-    def apply_provider(self, mediator: Mediator[T], request: Request[T]) -> T:
-        """Handle request instance and return a value of type required by request.
-        Behavior must be the same during the provider object lifetime
-
-        :raise CannotProvide: provider cannot process passed request
-        """
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        ...
diff --git a/src/adaptix/_internal/provider/facade/provider.py b/src/adaptix/_internal/provider/facade/provider.py
index a5bb4362..395b4939 100644
--- a/src/adaptix/_internal/provider/facade/provider.py
+++ b/src/adaptix/_internal/provider/facade/provider.py
@@ -1,17 +1,17 @@
-from typing import Sequence
+from collections.abc import Sequence
 
 from ...utils import Omitted
 from ..essential import Provider
 from ..loc_stack_filtering import OrLocStackChecker, Pred, create_loc_stack_checker
-from ..provider_wrapper import BoundingProvider
+from ..located_request import LocStackBoundingProvider
 
 
 def bound_by_any(preds: Sequence[Pred], provider: Provider) -> Provider:
     if len(preds) == 0:
         return provider
     if len(preds) == 1:
-        return BoundingProvider(create_loc_stack_checker(preds[0]), provider)
-    return BoundingProvider(
+        return LocStackBoundingProvider(create_loc_stack_checker(preds[0]), provider)
+    return LocStackBoundingProvider(
         OrLocStackChecker([create_loc_stack_checker(pred) for pred in preds]),
         provider,
     )
@@ -20,4 +20,4 @@ def bound_by_any(preds: Sequence[Pred], provider: Provider) -> Provider:
 def bound(pred: Pred, provider: Provider) -> Provider:
     if pred == Omitted():
         return provider
-    return BoundingProvider(create_loc_stack_checker(pred), provider)
+    return LocStackBoundingProvider(create_loc_stack_checker(pred), provider)
diff --git a/src/adaptix/_internal/provider/loc_stack_filtering.py b/src/adaptix/_internal/provider/loc_stack_filtering.py
index f480d0c7..90bc1b61 100644
--- a/src/adaptix/_internal/provider/loc_stack_filtering.py
+++ b/src/adaptix/_internal/provider/loc_stack_filtering.py
@@ -2,13 +2,16 @@
 import operator
 import re
 from abc import ABC, abstractmethod
+from collections.abc import Iterable, Sequence
 from copy import copy
-from dataclasses import dataclass
+from dataclasses import dataclass, replace
 from functools import reduce
 from inspect import isabstract, isgenerator
-from typing import Any, Callable, ClassVar, Iterable, Optional, Pattern, Protocol, Sequence, Type, TypeVar, Union, final
+from re import Pattern
+from typing import Any, ClassVar, Optional, TypeVar, Union, final
 
 from ..common import TypeHint, VarTuple
+from ..datastructures import ImmutableStack
 from ..type_tools import (
     BaseNormType,
     NormTV,
@@ -20,39 +23,16 @@
     normalize_type,
 )
 from ..type_tools.normalize_type import NotSubscribedError
-from .essential import CannotProvide, Request
-from .location import FieldLoc, GenericParamLoc, TypeHintLoc
-from .request_cls import LocStack
+from .essential import DirectMediator
+from .location import AnyLoc, FieldLoc, GenericParamLoc, TypeHintLoc
 
-T = TypeVar("T")
+LocStackT = TypeVar("LocStackT", bound="LocStack")
+AnyLocT_co = TypeVar("AnyLocT_co", bound=AnyLoc, covariant=True)
 
 
-class DirectMediator(Protocol):
-    """This is a copy of Mediator protocol but without provide_from_next() method"""
-
-    def provide(self, request: Request[T]) -> T:
-        ...
-
-    def delegating_provide(
-        self,
-        request: Request[T],
-        error_describer: Optional[Callable[[CannotProvide], str]] = None,
-    ) -> T:
-        ...
-
-    def mandatory_provide(
-        self,
-        request: Request[T],
-        error_describer: Optional[Callable[[CannotProvide], str]] = None,
-    ) -> T:
-        ...
-
-    def mandatory_provide_by_iterable(
-        self,
-        requests: Iterable[Request[T]],
-        error_describer: Optional[Callable[[], str]] = None,
-    ) -> Iterable[T]:
-        ...
+class LocStack(ImmutableStack[AnyLocT_co]):
+    def replace_last_type(self: LocStackT, tp: TypeHint, /) -> LocStackT:
+        return self.replace_last(replace(self.last, type=tp))
 
 
 class LocStackChecker(ABC):
@@ -119,7 +99,7 @@ def _reduce(self, elements: Iterable[bool], /) -> bool:
         return reduce(operator.xor, elements)
 
 
-class LastLocMapChecker(LocStackChecker, ABC):
+class LastLocChecker(LocStackChecker, ABC):
     _expected_location: ClassVar[type]
 
     def __init_subclass__(cls, **kwargs):
@@ -139,7 +119,7 @@ def _check_location(self, mediator: DirectMediator, loc: Any) -> bool:
 
 
 @dataclass(frozen=True)
-class ExactFieldNameLSC(LastLocMapChecker):
+class ExactFieldNameLSC(LastLocChecker):
     field_id: str
 
     def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> bool:
@@ -147,7 +127,7 @@ def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> bool:
 
 
 @dataclass(frozen=True)
-class ReFieldNameLSC(LastLocMapChecker):
+class ReFieldNameLSC(LastLocChecker):
     pattern: Pattern[str]
 
     def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> bool:
@@ -155,7 +135,7 @@ def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> bool:
 
 
 @dataclass(frozen=True)
-class ExactTypeLSC(LastLocMapChecker):
+class ExactTypeLSC(LastLocChecker):
     norm: BaseNormType
 
     def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
@@ -167,7 +147,7 @@ def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
 
 
 @dataclass(frozen=True)
-class OriginSubclassLSC(LastLocMapChecker):
+class OriginSubclassLSC(LastLocChecker):
     type_: type
 
     def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
@@ -179,7 +159,7 @@ def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
 
 
 @dataclass(frozen=True)
-class ExactOriginLSC(LastLocMapChecker):
+class ExactOriginLSC(LastLocChecker):
     origin: Any
 
     def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
@@ -191,7 +171,7 @@ def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
 
 
 @dataclass(frozen=True)
-class GenericParamLSC(LastLocMapChecker):
+class GenericParamLSC(LastLocChecker):
     pos: int
 
     def _check_location(self, mediator: DirectMediator, loc: GenericParamLoc) -> bool:
@@ -297,7 +277,7 @@ def ANY(self) -> AnyLocStackChecker:  # noqa: N802
         return _ANY
 
     @classmethod
-    def _from_lsc(cls: Type[Pat], lsc: LocStackChecker) -> Pat:
+    def _from_lsc(cls: type[Pat], lsc: LocStackChecker) -> Pat:
         return cls((lsc, ))
 
     def _extend_stack(self: Pat, elements: Iterable[LocStackChecker]) -> Pat:
diff --git a/src/adaptix/_internal/provider/loc_stack_tools.py b/src/adaptix/_internal/provider/loc_stack_tools.py
new file mode 100644
index 00000000..f4d0113e
--- /dev/null
+++ b/src/adaptix/_internal/provider/loc_stack_tools.py
@@ -0,0 +1,43 @@
+
+from ..common import TypeHint
+from ..type_tools import is_parametrized
+from ..utils import pairs
+from .loc_stack_filtering import LocStack
+from .location import AnyLoc, FieldLoc, InputFuncFieldLoc, TypeHintLoc
+
+
+def _format_type(tp: TypeHint) -> str:
+    if isinstance(tp, type) and not is_parametrized(tp):
+        return tp.__qualname__
+    str_tp = str(tp)
+    if str_tp.startswith("typing."):
+        return str_tp[7:]
+    return str_tp
+
+
+def format_loc_stack(loc_stack: LocStack[AnyLoc]) -> str:
+    fmt_tp = _format_type(loc_stack.last.type)
+
+    try:
+        field_loc = loc_stack.last.cast(FieldLoc)
+    except TypeError:
+        return fmt_tp
+    else:
+        fmt_field = f"{field_loc.field_id}: {fmt_tp}"
+
+    if loc_stack.last.is_castable(InputFuncFieldLoc):
+        func_field_loc = loc_stack.last.cast(InputFuncFieldLoc)
+        func_name = getattr(func_field_loc.func, "__qualname__", None) or repr(func_field_loc.func)
+        return f"{func_name}({fmt_field})"
+
+    if len(loc_stack) >= 2:  # noqa: PLR2004
+        src_owner = _format_type(loc_stack[-2].type)
+        return f"{src_owner}.{fmt_field}"
+    return fmt_tp
+
+
+def find_owner_with_field(stack: LocStack) -> tuple[TypeHintLoc, FieldLoc]:
+    for next_loc, prev_loc in pairs(reversed(stack)):
+        if next_loc.is_castable(FieldLoc):
+            return prev_loc, next_loc
+    raise ValueError
diff --git a/src/adaptix/_internal/provider/located_request.py b/src/adaptix/_internal/provider/located_request.py
new file mode 100644
index 00000000..6e695c71
--- /dev/null
+++ b/src/adaptix/_internal/provider/located_request.py
@@ -0,0 +1,114 @@
+from abc import ABC, abstractmethod
+from collections.abc import Sequence
+from dataclasses import dataclass, replace
+from typing import TypeVar, final
+
+from ..common import TypeHint, VarTuple
+from .essential import DirectMediator, Mediator, Provider, Request, RequestChecker, RequestHandler
+from .loc_stack_filtering import AnyLocStackChecker, LocStack, LocStackChecker, Pred, create_loc_stack_checker
+from .location import AnyLoc
+from .methods_provider import MethodsProvider
+from .request_checkers import AlwaysTrueRequestChecker
+
+T = TypeVar("T")
+
+LR = TypeVar("LR", bound="LocatedRequest")
+
+
+@dataclass(frozen=True)
+class LocatedRequest(Request[T]):
+    loc_stack: LocStack
+
+    @property
+    def last_loc(self) -> AnyLoc:
+        return self.loc_stack.last
+
+    def append_loc(self: LR, loc: AnyLoc) -> LR:
+        return replace(self, loc_stack=self.loc_stack.append_with(loc))
+
+
+class LocatedRequestChecker(RequestChecker[LocatedRequest]):
+    __slots__ = ("loc_stack_checker", )
+
+    def __init__(self, loc_stack_checker: LocStackChecker):
+        self.loc_stack_checker = loc_stack_checker
+
+    def check_request(self, mediator: DirectMediator, request: LocatedRequest, /) -> bool:
+        return self.loc_stack_checker.check_loc_stack(mediator, request.loc_stack)
+
+
+class LocatedRequestMethodsProvider(MethodsProvider):
+    _loc_stack_checker: LocStackChecker = AnyLocStackChecker()
+
+    @classmethod
+    def _validate_request_cls(cls, request_cls: type[Request]) -> None:
+        if not issubclass(request_cls, LocatedRequest):
+            raise TypeError(
+                f"@method_handler of {LocatedRequestMethodsProvider} can process only child of {LocatedRequest}",
+            )
+
+    def _get_request_checker(self) -> RequestChecker:
+        return LocatedRequestChecker(self._loc_stack_checker)
+
+
+def for_predicate(pred: Pred):
+    def decorator(cls: type[LocatedRequestMethodsProvider]):
+        if not (isinstance(cls, type) and issubclass(cls, LocatedRequestMethodsProvider)):
+            raise TypeError(f"Only {LocatedRequestMethodsProvider} child is allowed")
+
+        cls._loc_stack_checker = create_loc_stack_checker(pred)
+        return cls
+
+    return decorator
+
+
+class LocStackBoundingProvider(Provider):
+    def __init__(self, loc_stack_checker: LocStackChecker, provider: Provider):
+        self._loc_stack_checker = loc_stack_checker
+        self._provider = provider
+
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        return [
+            (request_cls, self._process_request_checker(request_cls, checker), handler)
+            for request_cls, checker, handler in self._provider.get_request_handlers()
+        ]
+
+    def _process_request_checker(self, request_cls: type[Request], checker: RequestChecker) -> RequestChecker:
+        if issubclass(request_cls, LocatedRequest):
+            if isinstance(checker, AlwaysTrueRequestChecker):
+                return LocatedRequestChecker(self._loc_stack_checker)
+            if isinstance(checker, LocatedRequestChecker):
+                return LocatedRequestChecker(self._loc_stack_checker & checker.loc_stack_checker)
+        return checker
+
+
+LocatedRequestT = TypeVar("LocatedRequestT", bound=LocatedRequest)
+
+
+class LocatedRequestDelegatingProvider(Provider, ABC):
+    REQUEST_CLASSES: VarTuple[type[LocatedRequest]] = ()
+
+    @final
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        request_checker = self.get_request_checker()
+
+        def delegating_request_handler(mediator, request):
+            tp = self.get_delegated_type(mediator, request)
+            return mediator.delegating_provide(
+                replace(
+                    request,
+                    loc_stack=request.loc_stack.replace_last_type(tp),
+                ),
+            )
+
+        return [
+            (request_cls, request_checker, delegating_request_handler)
+            for request_cls in self.REQUEST_CLASSES
+        ]
+
+    def get_request_checker(self) -> RequestChecker:
+        return AlwaysTrueRequestChecker()
+
+    @abstractmethod
+    def get_delegated_type(self, mediator: Mediator[LocatedRequestT], request: LocatedRequestT) -> TypeHint:
+        ...
diff --git a/src/adaptix/_internal/provider/location.py b/src/adaptix/_internal/provider/location.py
index 0f510206..777b66d6 100644
--- a/src/adaptix/_internal/provider/location.py
+++ b/src/adaptix/_internal/provider/location.py
@@ -1,5 +1,6 @@
+from collections.abc import Container, Mapping
 from dataclasses import dataclass, field
-from typing import Any, Callable, Container, Dict, Mapping, Type, TypeVar, Union
+from typing import Any, Callable, TypeVar, Union
 
 from ..common import TypeHint
 from ..model_tools.definitions import Accessor, Default
@@ -10,17 +11,17 @@
 class _BaseLoc:
     def cast_or_raise(
         self,
-        tp: Type[T],
-        exception_factory: Callable[[], Union[BaseException, Type[BaseException]]],
+        tp: type[T],
+        exception_factory: Callable[[], Union[BaseException, type[BaseException]]],
     ) -> T:
         if type(self) in _CAST_SOURCES[tp]:
             return self  # type: ignore[return-value]
         raise exception_factory()
 
-    def cast(self, tp: Type[T]) -> T:
+    def cast(self, tp: type[T]) -> T:
         return self.cast_or_raise(tp, lambda: TypeError(f"Can not cast {self} to {tp}"))
 
-    def is_castable(self, tp: Type[T]) -> bool:
+    def is_castable(self, tp: type[T]) -> bool:
         return type(self) in _CAST_SOURCES[tp]
 
 
@@ -87,7 +88,7 @@ class GenericParamLoc(_GenericParamLoc):
     pass
 
 
-_CAST_SOURCES: Dict[Any, Container[Any]] = {
+_CAST_SOURCES: dict[Any, Container[Any]] = {
     TypeHintLoc: {TypeHintLoc, FieldLoc, InputFieldLoc, OutputFieldLoc, GenericParamLoc, InputFuncFieldLoc},
     FieldLoc: {FieldLoc, InputFieldLoc, OutputFieldLoc, InputFuncFieldLoc},
     InputFieldLoc: {InputFieldLoc, InputFuncFieldLoc},
diff --git a/src/adaptix/_internal/provider/methods_provider.py b/src/adaptix/_internal/provider/methods_provider.py
new file mode 100644
index 00000000..824eb0b3
--- /dev/null
+++ b/src/adaptix/_internal/provider/methods_provider.py
@@ -0,0 +1,155 @@
+import inspect
+from collections.abc import Iterable, Mapping, Sequence
+from typing import Any, Callable, ClassVar, TypeVar, final
+
+from ..type_tools import get_all_type_hints, is_subclass_soft, normalize_type, strip_tags
+from .essential import Mediator, Provider, Request, RequestChecker, RequestHandler
+from .request_checkers import AlwaysTrueRequestChecker
+
+__all__ = ("MethodsProvider", "method_handler")
+
+
+P = TypeVar("P", bound=Any)
+R = TypeVar("R", bound=Request)
+T = TypeVar("T")
+MethodHandler = Callable[[P, Mediator[T], R], T]
+
+_METHOD_HANDLER_REQUEST_CLS = "_method_handler_request_cls"
+
+
+def method_handler(func: MethodHandler[P, T, R], /) -> MethodHandler[P, T, R]:
+    """Marks method as request handler. See :class:`MethodsProvider` for details"""
+    request_cls = _infer_request_cls(func)
+    setattr(func, _METHOD_HANDLER_REQUEST_CLS, request_cls)
+    return func
+
+
+def _infer_request_cls(func) -> type[Request]:
+    signature = inspect.signature(func)
+
+    params = list(signature.parameters.values())
+
+    if len(params) < 3:  # noqa: PLR2004
+        raise ValueError("Can not infer request class from callable")
+
+    if params[2].annotation == signature.empty:
+        raise ValueError("Can not infer request class from callable")
+
+    type_hints = get_all_type_hints(func)
+    request_tp = strip_tags(normalize_type(type_hints[params[2].name]))
+
+    if is_subclass_soft(request_tp.origin, Request):
+        return request_tp.source
+
+    raise TypeError("Request parameter must be subclass of Request")
+
+
+class MethodsProvider(Provider):
+    _mp_cls_request_to_method_name: ClassVar[Mapping[type[Request], str]] = {}
+
+    def __init_subclass__(cls, **kwargs):
+        own_spa = _collect_class_own_request_cls_dict(cls)
+
+        parent_request_cls_dicts = [
+            parent._mp_cls_request_to_method_name
+            for parent in cls.__bases__
+            if issubclass(parent, MethodsProvider)
+        ]
+        cls._mp_cls_request_to_method_name = _merge_request_cls_dicts(cls, [*parent_request_cls_dicts, own_spa])
+        for request_cls in cls._mp_cls_request_to_method_name:
+            cls._validate_request_cls(request_cls)
+
+    @classmethod
+    def _validate_request_cls(cls, request_cls: type[Request]) -> None:
+        pass
+
+    def _get_request_checker(self) -> RequestChecker:
+        return AlwaysTrueRequestChecker()
+
+    @final
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        request_checker = self._get_request_checker()
+        return [
+            (request_cls, request_checker, getattr(self, method_name))
+            for request_cls, method_name in self._mp_cls_request_to_method_name.items()
+        ]
+
+
+def _request_cls_attached_to_several_method_handlers(
+    cls: type,
+    name1: str,
+    name2: str,
+    request_cls: type[Request],
+):
+    return TypeError(
+        f"The {cls} has several @method_handler"
+        " that attached to the same Request class"
+        f" ({name1!r} and {name2!r} attached to {request_cls})",
+    )
+
+
+def _method_handler_has_different_request_cls(
+    cls: type,
+    name: str,
+    request_cls1: type[Request],
+    request_cls2: type[Request],
+):
+    return TypeError(
+        f"The {cls} has @method_handler"
+        " that attached to the different Request class"
+        f" ({name!r} attached to {request_cls1} and {request_cls2})",
+    )
+
+
+_RequestClsToMethodName = dict[type[Request], str]
+
+
+def _collect_class_own_request_cls_dict(cls) -> _RequestClsToMethodName:
+    mapping: _RequestClsToMethodName = {}
+
+    for attr_name in vars(cls):
+        try:
+            attr_value = getattr(cls, attr_name)
+        except AttributeError:
+            continue
+        if hasattr(attr_value, _METHOD_HANDLER_REQUEST_CLS):
+            request_cls = getattr(attr_value, _METHOD_HANDLER_REQUEST_CLS)
+            if request_cls in mapping:
+                old_name = mapping[request_cls]
+                raise _request_cls_attached_to_several_method_handlers(
+                    cls,
+                    attr_name,
+                    old_name,
+                    request_cls,
+                )
+
+            mapping[request_cls] = attr_name
+
+    return mapping
+
+
+def _merge_request_cls_dicts(cls: type, dict_iter: Iterable[_RequestClsToMethodName]) -> _RequestClsToMethodName:
+    name_to_request_cls: dict[str, type[Request]] = {}
+    request_cls_to_name: _RequestClsToMethodName = {}
+    for dct in dict_iter:
+        for request_cls, name in dct.items():
+            if request_cls in request_cls_to_name and request_cls_to_name[request_cls] != name:
+                raise _request_cls_attached_to_several_method_handlers(
+                    cls,
+                    request_cls_to_name[request_cls],
+                    name,
+                    request_cls,
+                )
+
+            if name in name_to_request_cls and request_cls != name_to_request_cls[name]:
+                raise _method_handler_has_different_request_cls(
+                    cls,
+                    name,
+                    name_to_request_cls[name],
+                    request_cls,
+                )
+
+            request_cls_to_name[request_cls] = name
+            name_to_request_cls[name] = request_cls
+
+    return request_cls_to_name
diff --git a/src/adaptix/_internal/provider/overlay_schema.py b/src/adaptix/_internal/provider/overlay_schema.py
index b718ed62..2c53139b 100644
--- a/src/adaptix/_internal/provider/overlay_schema.py
+++ b/src/adaptix/_internal/provider/overlay_schema.py
@@ -1,13 +1,15 @@
+from collections.abc import Iterable, Mapping
 from dataclasses import dataclass, fields
-from typing import Any, Callable, ClassVar, Generic, Iterable, Mapping, Optional, Type, TypeVar
+from typing import Any, Callable, ClassVar, Generic, Optional, TypeVar
 
 from ..datastructures import ClassMap
 from ..type_tools import strip_alias
 from ..utils import Omitted
 from .essential import CannotProvide, Mediator
+from .loc_stack_filtering import LocStack
+from .located_request import LocatedRequest
+from .methods_provider import MethodsProvider, method_handler
 from .provider_wrapper import Chain
-from .request_cls import LocatedRequest, LocStack
-from .static_provider import StaticProvider, static_provision_action
 
 
 @dataclass(frozen=True)
@@ -23,7 +25,7 @@ class Schema:
 
 @dataclass(frozen=True)
 class Overlay(Generic[Sc]):
-    _schema_cls: ClassVar[Type[Schema]]  # ClassVar cannot contain TypeVar
+    _schema_cls: ClassVar[type[Schema]]  # ClassVar cannot contain TypeVar
     _mergers: ClassVar[Optional[Mapping[str, Merger]]]
 
     def __init_subclass__(cls, *args, **kwargs):
@@ -79,10 +81,10 @@ def to_schema(self) -> Sc:
 
 @dataclass(frozen=True)
 class OverlayRequest(LocatedRequest[Ov], Generic[Ov]):
-    overlay_cls: Type[Ov]
+    overlay_cls: type[Ov]
 
 
-def provide_schema(overlay: Type[Overlay[Sc]], mediator: Mediator, loc_stack: LocStack) -> Sc:
+def provide_schema(overlay: type[Overlay[Sc]], mediator: Mediator, loc_stack: LocStack) -> Sc:
     stacked_overlay = mediator.mandatory_provide(
         OverlayRequest(
             loc_stack=loc_stack,
@@ -106,12 +108,12 @@ def provide_schema(overlay: Type[Overlay[Sc]], mediator: Mediator, loc_stack: Lo
     return stacked_overlay.to_schema()
 
 
-class OverlayProvider(StaticProvider):
+class OverlayProvider(MethodsProvider):
     def __init__(self, overlays: Iterable[Overlay], chain: Optional[Chain]):
         self._chain = chain
         self._overlays = ClassMap(*overlays)
 
-    @static_provision_action
+    @method_handler
     def _provide_overlay(self, mediator: Mediator, request: OverlayRequest):
         try:
             overlay = self._overlays[request.overlay_cls]
diff --git a/src/adaptix/_internal/provider/provider_template.py b/src/adaptix/_internal/provider/provider_template.py
deleted file mode 100644
index 243e54d4..00000000
--- a/src/adaptix/_internal/provider/provider_template.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from typing import Generic, Optional, Type, TypeVar
-
-from .essential import CannotProvide, Mediator, Request
-from .loc_stack_filtering import LocStackChecker, P, Pred, create_loc_stack_checker
-from .provider_wrapper import ProviderWithLSC, RequestClassDeterminedProvider
-from .static_provider import StaticProvider
-
-T = TypeVar("T")
-
-
-class ProviderWithAttachableLSC(StaticProvider, ProviderWithLSC):
-    _loc_stack_checker: LocStackChecker = P.ANY
-
-    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
-        return self._loc_stack_checker
-
-
-def for_predicate(pred: Pred):
-    def decorator(cls: Type[ProviderWithAttachableLSC]):
-        if not (isinstance(cls, type) and issubclass(cls, ProviderWithAttachableLSC)):
-            raise TypeError(f"Only {ProviderWithAttachableLSC} child is allowed")
-
-        # noinspection PyProtectedMember
-        cls._loc_stack_checker = create_loc_stack_checker(pred)
-        return cls
-
-    return decorator
-
-
-class ValueProvider(RequestClassDeterminedProvider, Generic[T]):
-    def __init__(self, request_cls: Type[Request[T]], value: T):
-        self._request_cls = request_cls
-        self._value = value
-
-    def apply_provider(self, mediator: Mediator, request: Request):
-        if not isinstance(request, self._request_cls):
-            raise CannotProvide
-
-        return self._value
-
-    def __repr__(self):
-        return f"{type(self).__name__}({self._request_cls}, {self._value})"
-
-    def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
-        return issubclass(request_cls, self._request_cls)
diff --git a/src/adaptix/_internal/provider/provider_wrapper.py b/src/adaptix/_internal/provider/provider_wrapper.py
index e5f5bbfe..58b6b1e7 100644
--- a/src/adaptix/_internal/provider/provider_wrapper.py
+++ b/src/adaptix/_internal/provider/provider_wrapper.py
@@ -1,111 +1,64 @@
-from abc import ABC, abstractmethod
+import itertools
+from collections.abc import Sequence
 from enum import Enum
-from typing import Optional, Type, TypeVar
+from typing import TypeVar
 
-from .essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
-from .loc_stack_filtering import LocStackChecker
-from .request_cls import LocatedRequest
+from .essential import Mediator, Provider, Request, RequestChecker, RequestHandler
 
 T = TypeVar("T")
 
 
-class RequestClassDeterminedProvider(Provider, ABC):
-    @abstractmethod
-    def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
-        ...
-
-
-class ProviderWithLSC(Provider, ABC):
-    @abstractmethod
-    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
-        ...
-
-    def _apply_loc_stack_checker(self, mediator: Mediator, request: Request) -> None:
-        if not isinstance(request, LocatedRequest):
-            raise CannotProvide
-
-        loc_stack_checker = self.get_loc_stack_checker()
-        if loc_stack_checker is None:
-            return
-
-        if not loc_stack_checker.check_loc_stack(mediator, request.loc_stack):
-            raise CannotProvide
-
-
-class BoundingProvider(RequestClassDeterminedProvider, ProviderWithLSC):
-    def __init__(self, loc_stack_checker: LocStackChecker, provider: Provider):
-        self._loc_stack_checker = loc_stack_checker
-        self._provider = provider
-
-    def apply_provider(self, mediator: Mediator, request: Request[T]) -> T:
-        self._apply_loc_stack_checker(mediator, request)
-        return self._provider.apply_provider(mediator, request)
-
-    def __repr__(self):
-        return f"{type(self).__name__}({self._loc_stack_checker}, {self._provider})"
-
-    def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
-        if isinstance(self._provider, RequestClassDeterminedProvider):
-            return self._provider.maybe_can_process_request_cls(request_cls)
-        return True
-
-    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
-        return self._loc_stack_checker
-
-
-class ConcatProvider(RequestClassDeterminedProvider):
+class ConcatProvider(Provider):
     def __init__(self, *providers: Provider):
         self._providers = providers
 
-    def apply_provider(self, mediator: Mediator[T], request: Request[T]) -> T:
-        exceptions = []
-
-        for provider in self._providers:
-            try:
-                return provider.apply_provider(mediator, request)
-            except CannotProvide as e:
-                exceptions.append(e)
-
-        raise AggregateCannotProvide.make("", exceptions)
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        return list(
+            itertools.chain.from_iterable(
+                provider.get_request_handlers()
+                for provider in self._providers
+            ),
+        )
 
     def __repr__(self):
         return f"{type(self).__name__}({self._providers})"
 
-    def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
-        return any(
-            not isinstance(provider, RequestClassDeterminedProvider)
-            or provider.maybe_can_process_request_cls(request_cls)
-            for provider in self._providers
-        )
-
 
 class Chain(Enum):
     FIRST = "FIRST"
     LAST = "LAST"
 
 
-class ChainingProvider(RequestClassDeterminedProvider):
+RequestT = TypeVar("RequestT", bound=Request)
+ResponseT = TypeVar("ResponseT")
+
+
+class ChainingProvider(Provider):
     def __init__(self, chain: Chain, provider: Provider):
         self._chain = chain
         self._provider = provider
 
-    def apply_provider(self, mediator: Mediator[T], request: Request[T]) -> T:
-        current_processor = self._provider.apply_provider(mediator, request)
-        next_processor = mediator.provide_from_next()
-
-        if self._chain == Chain.FIRST:
-            return self._make_chain(current_processor, next_processor)
-        if self._chain == Chain.LAST:
-            return self._make_chain(next_processor, current_processor)
-        raise ValueError
-
     def _make_chain(self, first, second):
         def chain_processor(data):
             return second(first(data))
 
         return chain_processor
 
-    def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
-        if isinstance(self._provider, RequestClassDeterminedProvider):
-            return self._provider.maybe_can_process_request_cls(request_cls)
-        return True
+    def _wrap_handler(self, handler: RequestHandler[ResponseT, RequestT]) -> RequestHandler[ResponseT, RequestT]:
+        def chaining_handler(mediator: Mediator[ResponseT], request: RequestT) -> ResponseT:
+            current_processor = handler(mediator, request)
+            next_processor = mediator.provide_from_next()
+
+            if self._chain == Chain.FIRST:
+                return self._make_chain(current_processor, next_processor)
+            if self._chain == Chain.LAST:
+                return self._make_chain(next_processor, current_processor)
+            raise ValueError
+
+        return chaining_handler
+
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        return [
+            (request_cls, checker, self._wrap_handler(handler))
+            for request_cls, checker, handler in self._provider.get_request_handlers()
+        ]
diff --git a/src/adaptix/_internal/provider/request_checkers.py b/src/adaptix/_internal/provider/request_checkers.py
new file mode 100644
index 00000000..0e5fa58b
--- /dev/null
+++ b/src/adaptix/_internal/provider/request_checkers.py
@@ -0,0 +1,11 @@
+from adaptix._internal.provider.essential import DirectMediator, Request, RequestChecker
+
+
+class AlwaysTrueRequestChecker(RequestChecker):
+    def check_request(self, mediator: DirectMediator, request: Request, /) -> bool:
+        return True
+
+    def __eq__(self, other):
+        if isinstance(other, type(self)):
+            return True
+        return NotImplemented
diff --git a/src/adaptix/_internal/provider/request_cls.py b/src/adaptix/_internal/provider/request_cls.py
deleted file mode 100644
index 1dbc477d..00000000
--- a/src/adaptix/_internal/provider/request_cls.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from dataclasses import dataclass, replace
-from typing import Tuple, TypeVar
-
-from ..common import TypeHint
-from ..datastructures import ImmutableStack
-from ..definitions import DebugTrail
-from ..type_tools import BaseNormType, is_parametrized, normalize_type
-from ..utils import pairs
-from .essential import CannotProvide, Request
-from .location import AnyLoc, FieldLoc, InputFuncFieldLoc, TypeHintLoc
-
-LocStackT = TypeVar("LocStackT", bound="LocStack")
-AnyLocT_co = TypeVar("AnyLocT_co", bound=AnyLoc, covariant=True)
-
-
-class LocStack(ImmutableStack[AnyLocT_co]):
-    def replace_last_type(self: LocStackT, tp: TypeHint, /) -> LocStackT:
-        return self.replace_last(replace(self.last, type=tp))
-
-
-def _format_type(tp: TypeHint) -> str:
-    if isinstance(tp, type) and not is_parametrized(tp):
-        return tp.__qualname__
-    str_tp = str(tp)
-    if str_tp.startswith("typing."):
-        return str_tp[7:]
-    return str_tp
-
-
-def format_loc_stack(loc_stack: LocStack[AnyLoc]) -> str:
-    fmt_tp = _format_type(loc_stack.last.type)
-
-    try:
-        field_loc = loc_stack.last.cast(FieldLoc)
-    except TypeError:
-        return fmt_tp
-    else:
-        fmt_field = f"{field_loc.field_id}: {fmt_tp}"
-
-    if loc_stack.last.is_castable(InputFuncFieldLoc):
-        func_field_loc = loc_stack.last.cast(InputFuncFieldLoc)
-        func_name = getattr(func_field_loc.func, "__qualname__", None) or repr(func_field_loc.func)
-        return f"{func_name}({fmt_field})"
-
-    if len(loc_stack) >= 2:  # noqa: PLR2004
-        src_owner = _format_type(loc_stack[-2].type)
-        return f"{src_owner}.{fmt_field}"
-    return fmt_tp
-
-
-T = TypeVar("T")
-
-
-@dataclass(frozen=True)
-class LocatedRequest(Request[T]):
-    loc_stack: LocStack
-
-    @property
-    def last_loc(self) -> AnyLoc:
-        return self.loc_stack.last
-
-
-def get_type_from_request(request: LocatedRequest) -> TypeHint:
-    return request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide).type
-
-
-def try_normalize_type(tp: TypeHint) -> BaseNormType:
-    try:
-        return normalize_type(tp)
-    except ValueError:
-        raise CannotProvide(f"{tp} can not be normalized")
-
-
-class StrictCoercionRequest(LocatedRequest[bool]):
-    pass
-
-
-class DebugTrailRequest(LocatedRequest[DebugTrail]):
-    pass
-
-
-def find_owner_with_field(stack: LocStack) -> Tuple[TypeHintLoc, FieldLoc]:
-    for next_loc, prev_loc in pairs(reversed(stack)):
-        if next_loc.is_castable(FieldLoc):
-            return prev_loc, next_loc
-    raise ValueError
diff --git a/src/adaptix/_internal/provider/shape_provider.py b/src/adaptix/_internal/provider/shape_provider.py
index f32a970a..bda7955f 100644
--- a/src/adaptix/_internal/provider/shape_provider.py
+++ b/src/adaptix/_internal/provider/shape_provider.py
@@ -1,6 +1,7 @@
 import inspect
+from collections.abc import Container, Iterable
 from dataclasses import dataclass, replace
-from typing import Any, Container, Generic, Iterable, Optional, TypeVar, Union, cast
+from typing import Any, Generic, Optional, TypeVar, Union, cast
 
 from ..common import TypeHint
 from ..model_tools.definitions import (
@@ -24,10 +25,10 @@
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import create_loc_stack_checker
 from ..type_tools.generic_resolver import GenericResolver, MembersStorage
-from .provider_template import ProviderWithAttachableLSC
+from .essential import RequestChecker
+from .located_request import LocatedRequest, LocatedRequestChecker
+from .methods_provider import MethodsProvider, method_handler
 from .provider_wrapper import ConcatProvider
-from .request_cls import LocatedRequest, TypeHintLoc
-from .static_provider import StaticProvider, static_provision_action
 
 
 @dataclass(frozen=True)
@@ -40,7 +41,7 @@ class OutputShapeRequest(LocatedRequest[OutputShape]):
     pass
 
 
-class ShapeProvider(StaticProvider):
+class ShapeProvider(MethodsProvider):
     def __init__(self, introspector: ShapeIntrospector):
         self._introspector = introspector
 
@@ -57,18 +58,16 @@ def _get_shape(self, tp) -> Shape:
         except IntrospectionError as e:
             raise CannotProvide from e
 
-    @static_provision_action
+    @method_handler
     def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -> InputShape:
-        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
-        shape = self._get_shape(loc.type)
+        shape = mediator.cached_call(self._get_shape, request.last_loc.type)
         if shape.input is None:
             raise CannotProvide
         return shape.input
 
-    @static_provision_action
+    @method_handler
     def _provide_output_shape(self, mediator: Mediator, request: OutputShapeRequest) -> OutputShape:
-        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
-        shape = self._get_shape(loc.type)
+        shape = mediator.cached_call(self._get_shape, request.last_loc.type)
         if shape.output is None:
             raise CannotProvide
         return shape.output
@@ -86,7 +85,7 @@ def _provide_output_shape(self, mediator: Mediator, request: OutputShapeRequest)
 )
 
 
-class PropertyExtender(StaticProvider):
+class PropertyExtender(MethodsProvider):
     def __init__(
         self,
         output_fields: Iterable[OutputField],
@@ -104,9 +103,9 @@ def __init__(
                 f" all fields must use DescriptorAccessor",
             )
 
-    @static_provision_action
+    @method_handler
     def _provide_output_shape(self, mediator: Mediator[OutputShape], request: OutputShapeRequest) -> OutputShape:
-        tp = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide).type
+        tp = request.last_loc.type
         shape = mediator.provide_from_next()
 
         additional_fields = tuple(
@@ -187,7 +186,7 @@ def provide_generic_resolved_shape(mediator: Mediator, request: LocatedRequest[S
 T = TypeVar("T")
 
 
-class SimilarShapeProvider(ProviderWithAttachableLSC):
+class SimilarShapeProvider(MethodsProvider):
     def __init__(self, target: TypeHint, prototype: TypeHint, *, for_input: bool = True, for_output: bool = True):
         self._target = target
         self._prototype = prototype
@@ -195,12 +194,14 @@ def __init__(self, target: TypeHint, prototype: TypeHint, *, for_input: bool = T
         self._for_input = for_input
         self._for_output = for_output
 
-    @static_provision_action
+    def _get_request_checker(self) -> RequestChecker:
+        return LocatedRequestChecker(self._loc_stack_checker)
+
+    @method_handler
     def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -> InputShape:
         if not self._for_input:
             raise CannotProvide
 
-        self._apply_loc_stack_checker(mediator, request)
         shape = mediator.delegating_provide(
             replace(
                 request,
@@ -209,12 +210,11 @@ def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -
         )
         return replace(shape, constructor=self._target)
 
-    @static_provision_action
+    @method_handler
     def _provide_output_shape(self, mediator: Mediator, request: OutputShapeRequest) -> OutputShape:
         if not self._for_output:
             raise CannotProvide
 
-        self._apply_loc_stack_checker(mediator, request)
         return mediator.delegating_provide(
             replace(
                 request,
diff --git a/src/adaptix/_internal/provider/static_provider.py b/src/adaptix/_internal/provider/static_provider.py
deleted file mode 100644
index d1e92fac..00000000
--- a/src/adaptix/_internal/provider/static_provider.py
+++ /dev/null
@@ -1,194 +0,0 @@
-import inspect
-from inspect import isfunction
-from typing import Callable, ClassVar, Dict, Iterable, Type, TypeVar, final, overload
-
-from ..datastructures import ClassDispatcher
-from ..type_tools import get_all_type_hints, is_subclass_soft, normalize_type, strip_tags
-from .essential import CannotProvide, Mediator, Provider, Request
-from .provider_wrapper import RequestClassDeterminedProvider
-
-__all__ = ("StaticProvider", "static_provision_action", "RequestDispatcher")
-
-RequestDispatcher = ClassDispatcher[Request, str]
-
-R = TypeVar("R", bound=Request)
-P = TypeVar("P", bound=Provider)
-T = TypeVar("T")
-SPA = Callable[[P, Mediator[T], R], T]
-
-_SPA_RC_STORAGE = "_spa_request_cls"
-
-
-@overload
-def static_provision_action() -> Callable[[SPA[P, T, R]], SPA[P, T, R]]:
-    ...
-
-
-@overload  # type: ignore[overload-overlap]
-def static_provision_action(request_cls: Type[Request], /) -> Callable[[SPA[P, T, R]], SPA[P, T, R]]:
-    ...
-
-
-@overload
-def static_provision_action(func: SPA[P, T, R], /) -> SPA[P, T, R]:
-    ...
-
-
-def static_provision_action(arg=None):
-    """Marks method as ``@static_provision_action``.
-    See :class:`StaticProvider` for details
-    """
-
-    if arg is None:
-        return static_provision_action
-
-    if is_subclass_soft(arg, Request):
-        return _make_spa_decorator(arg)
-
-    if isfunction(arg):
-        return _make_spa_decorator(_infer_rc(arg))(arg)
-
-    if hasattr(arg, "__func__"):
-        return _make_spa_decorator(_infer_rc(arg.__func__))(arg)
-
-    raise TypeError(
-        "static_provision_action must be applied"
-        " as @static_provision_action or @static_provision_action()"
-        " or @static_provision_action(Request)",
-    )
-
-
-def _infer_rc(func) -> Type[Request]:
-    signature = inspect.signature(func)
-
-    params = list(signature.parameters.values())
-
-    if len(params) < 3:  # noqa: PLR2004
-        raise ValueError("Can not infer request class from callable")
-
-    if params[2].annotation == signature.empty:
-        raise ValueError("Can not infer request class from callable")
-
-    type_hints = get_all_type_hints(func)
-    request_tp = strip_tags(normalize_type(type_hints[params[2].name]))
-
-    if is_subclass_soft(request_tp.origin, Request):
-        return request_tp.source
-
-    raise TypeError("Request parameter must be subclass of Request")
-
-
-def _make_spa_decorator(request_cls: Type[R]):
-    def spa_decorator(func: Callable[[P, Mediator, R], T]):
-        if hasattr(func, _SPA_RC_STORAGE):
-            raise ValueError("@static_provision_action decorator cannot be applied twice")
-
-        setattr(func, _SPA_RC_STORAGE, request_cls)
-        return func
-
-    return spa_decorator
-
-
-class StaticProvider(RequestClassDeterminedProvider):
-    """Provider which instances can process same set of Request classes.
-
-    Subclass defines provision actions wrapping method by decorator
-    ``@static_provision_action(request_cls)``. Argument of decorator attaching
-    method to specified Request class.
-    It means that that provision action will be called for specified
-    request, or it's subclass. See :class:`Provider` for details.
-
-    You can omit request_cls parameter and decorator try to infer it introspecting method signature.
-
-    Subclasses cannot have multiple methods attached to the same request.
-
-    During subclassing, ``StaticProvider`` goes through attributes of the class
-    and collects all methods wrapped by :func:`static_provision_action` decorator.
-    Then it merges list of new :func:`static_provision_action`'s with the parent ones.
-    """
-    _sp_cls_request_dispatcher: ClassVar[RequestDispatcher] = RequestDispatcher()
-
-    def __init_subclass__(cls, **kwargs):
-        own_spa = _collect_class_own_rc_dict(cls)
-
-        parent_rd_dicts = [
-            parent._sp_cls_request_dispatcher.to_dict()
-            for parent in cls.__bases__
-            if issubclass(parent, StaticProvider)
-        ]
-
-        result = _merge_rc_dicts(cls, [*parent_rd_dicts, own_spa])
-
-        cls._sp_cls_request_dispatcher = RequestDispatcher(result)
-
-    @final
-    def apply_provider(self, mediator: Mediator, request: Request[T]) -> T:
-        try:
-            attr_name = self._sp_cls_request_dispatcher.dispatch(type(request))
-        except KeyError:
-            raise CannotProvide
-
-        return getattr(self, attr_name)(mediator, request)
-
-    @final
-    def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
-        try:
-            self._sp_cls_request_dispatcher.dispatch(request_cls)
-        except KeyError:
-            return False
-        return True
-
-
-def _rc_attached_to_several_spa(cls: type, name1: str, name2: str, rc: Type[Request]):
-    return TypeError(
-        f"The {cls} has several @static_provision_action"
-        " that attached to the same Request class"
-        f" ({name1!r} and {name2!r} attached to {rc})",
-    )
-
-
-def _spa_has_different_rc(cls: type, name: str, rc1: Type[Request], rc2: Type[Request]):
-    return TypeError(
-        f"The {cls} has @static_provision_action"
-        " that attached to the different Request class"
-        f" ({name!r} attached to {rc1} and {rc2})",
-    )
-
-
-_RcDict = Dict[Type[Request], str]
-
-
-def _collect_class_own_rc_dict(cls) -> _RcDict:
-    mapping: _RcDict = {}
-
-    for attr_name in vars(cls):
-        try:
-            attr_value = getattr(cls, attr_name)
-        except AttributeError:
-            continue
-        if hasattr(attr_value, _SPA_RC_STORAGE):
-            rc = getattr(attr_value, _SPA_RC_STORAGE)
-            if rc in mapping:
-                old_name = mapping[rc]
-                raise _rc_attached_to_several_spa(cls, attr_name, old_name, rc)
-
-            mapping[rc] = attr_name
-
-    return mapping
-
-
-def _merge_rc_dicts(cls: type, dict_iter: Iterable[_RcDict]) -> _RcDict:
-    name_to_rc: Dict[str, Type[Request]] = {}
-    rc_to_name: _RcDict = {}
-    for dct in dict_iter:
-        for rc, name in dct.items():
-            if rc in rc_to_name:
-                raise _rc_attached_to_several_spa(cls, rc_to_name[rc], name, rc)
-
-            if name in name_to_rc and rc != name_to_rc[name]:
-                raise _spa_has_different_rc(cls, name, name_to_rc[name], rc)
-
-            rc_to_name[rc] = name
-            name_to_rc[name] = rc
-
-    return rc_to_name
diff --git a/src/adaptix/_internal/provider/value_provider.py b/src/adaptix/_internal/provider/value_provider.py
new file mode 100644
index 00000000..fd4336bc
--- /dev/null
+++ b/src/adaptix/_internal/provider/value_provider.py
@@ -0,0 +1,21 @@
+from collections.abc import Sequence
+from typing import Generic, TypeVar
+
+from .essential import Provider, Request, RequestChecker, RequestHandler
+from .request_checkers import AlwaysTrueRequestChecker
+
+T = TypeVar("T")
+
+
+class ValueProvider(Provider, Generic[T]):
+    def __init__(self, request_cls: type[Request[T]], value: T):
+        self._request_cls = request_cls
+        self._value = value
+
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        return [
+            (self._request_cls, AlwaysTrueRequestChecker(), lambda m, r: self._value),
+        ]
+
+    def __repr__(self):
+        return f"{type(self).__name__}({self._request_cls}, {self._value})"
diff --git a/src/adaptix/_internal/retort/base_retort.py b/src/adaptix/_internal/retort/base_retort.py
index 511765e2..2094dbff 100644
--- a/src/adaptix/_internal/retort/base_retort.py
+++ b/src/adaptix/_internal/retort/base_retort.py
@@ -1,11 +1,11 @@
-from abc import ABC, ABCMeta, abstractmethod
-from typing import ClassVar, Iterable, Sequence, TypeVar
+import itertools
+from abc import ABCMeta
+from collections.abc import Iterable, Sequence
+from typing import ClassVar, TypeVar
 
 from ..common import VarTuple
-from ..provider.essential import Mediator, Provider, Request
+from ..provider.essential import Provider
 from ..utils import Cloneable, ForbiddingDescriptor
-from .mediator import BuiltinMediator, ErrorRepresentor, RecursionResolver
-from .routing import IntrospectingRecipeSearcher, RecipeSearcher
 
 
 class RetortMeta(ABCMeta):  # inherits from ABCMeta to be compatible with ABC
@@ -29,27 +29,28 @@ def __new__(mcs, name, bases, namespace, **kwargs):
 T = TypeVar("T")
 
 
-class BaseRetort(Cloneable, ABC, metaclass=RetortMeta):
+class BaseRetort(Cloneable, metaclass=RetortMeta):
     recipe: ClassVar[Iterable[Provider]] = []
     _full_class_recipe: ClassVar[VarTuple[Provider]]
 
     def __init_subclass__(cls, **kwargs):
         # noinspection PyProtectedMember
-        recipe_sum = sum(
-            (
+        cls._full_class_recipe = tuple(
+            itertools.chain.from_iterable(
                 parent._own_class_recipe
                 for parent in cls.mro()
                 if isinstance(parent, RetortMeta)
             ),
-            start=(),
         )
-        cls._full_class_recipe = recipe_sum
 
-    def __init__(self, recipe: Iterable[Provider] = ()):
-        self._inc_instance_recipe = tuple(recipe)
+    def __init__(self, *, recipe: Iterable[Provider] = ()):
+        self._instance_recipe = tuple(recipe)
         self._calculate_derived()
 
-    def _get_config_recipe(self) -> VarTuple[Provider]:
+    def _get_recipe_head(self) -> Sequence[Provider]:
+        return ()
+
+    def _get_recipe_tail(self) -> Sequence[Provider]:
         return ()
 
     def _get_full_recipe(self) -> Sequence[Provider]:
@@ -57,38 +58,11 @@ def _get_full_recipe(self) -> Sequence[Provider]:
 
     def _calculate_derived(self) -> None:
         super()._calculate_derived()
-        self._full_recipe = (
-            self._inc_instance_recipe
-            + self._get_config_recipe()
-            + self._full_class_recipe
-        )
-        self._searcher = self._create_searcher(self._full_recipe)
-
-    def _create_searcher(self, full_recipe: Sequence[Provider]) -> RecipeSearcher:
-        return IntrospectingRecipeSearcher(full_recipe)
-
-    @abstractmethod
-    def _create_recursion_resolver(self) -> RecursionResolver:
-        ...
-
-    @abstractmethod
-    def _get_error_representor(self) -> ErrorRepresentor:
-        ...
-
-    def _create_mediator(self) -> Mediator:
-        recursion_resolver = self._create_recursion_resolver()
-        error_representor = self._get_error_representor()
-        return BuiltinMediator(
-            self._searcher,
-            recursion_resolver,
-            error_representor,
+        self._full_recipe = tuple(
+            itertools.chain(
+                self._get_recipe_head(),
+                self._instance_recipe,
+                self._full_class_recipe,
+                self._get_recipe_tail(),
+            ),
         )
-
-    def _provide_from_recipe(self, request: Request[T]) -> T:
-        """Process request iterating over the result of _get_full_recipe()
-        :param request:
-        :return: request result
-        :raise CannotProvide: request was not processed
-        """
-        mediator = self._create_mediator()
-        return mediator.provide(request)
diff --git a/src/adaptix/_internal/retort/builtin_mediator.py b/src/adaptix/_internal/retort/builtin_mediator.py
new file mode 100644
index 00000000..9fedbc14
--- /dev/null
+++ b/src/adaptix/_internal/retort/builtin_mediator.py
@@ -0,0 +1,60 @@
+from abc import ABC, abstractmethod
+from collections.abc import Mapping
+from typing import Any, Callable, Generic, TypeVar
+
+from ..provider.essential import CannotProvide, Mediator, Request
+
+T = TypeVar("T")
+
+
+RequestT = TypeVar("RequestT", bound=Request)
+ResponseT = TypeVar("ResponseT")
+
+
+class RequestBus(ABC, Generic[RequestT, ResponseT]):
+    @abstractmethod
+    def send(self, request: RequestT) -> ResponseT:
+        pass
+
+    @abstractmethod
+    def send_chaining(self, request: RequestT, search_offset: int) -> ResponseT:
+        pass
+
+
+class BuiltinMediator(Mediator[ResponseT], Generic[ResponseT]):
+    __slots__ = ("_request_buses", "_request", "_search_offset", "_no_request_bus_error_maker", "_call_cache")
+
+    def __init__(
+        self,
+        request_buses: Mapping[type[Request], RequestBus],
+        request: Request,
+        search_offset: int,
+        no_request_bus_error_maker: Callable[[Request], CannotProvide],
+        call_cache: dict[Any, Any],
+    ):
+        self._request_buses = request_buses
+        self._request = request
+        self._search_offset = search_offset
+        self._no_request_bus_error_maker = no_request_bus_error_maker
+        self._call_cache = call_cache
+
+    __hash__ = None  # type: ignore[assignment]
+
+    def provide(self, request: Request[T]) -> T:
+        try:
+            request_bus = self._request_buses[type(request)]
+        except KeyError:
+            raise self._no_request_bus_error_maker(request) from None
+
+        return request_bus.send(request)
+
+    def provide_from_next(self) -> ResponseT:
+        return self._request_buses[type(self._request)].send_chaining(self._request, self._search_offset)
+
+    def cached_call(self, func: Callable[..., T], /, *args: Any, **kwargs: Any) -> T:  # type: ignore[override]
+        key = (func, *args, *kwargs.items())
+        if key in self._call_cache:
+            return self._call_cache[key]
+        result = func(*args, **kwargs)
+        self._call_cache[key] = result
+        return result
diff --git a/src/adaptix/_internal/retort/mediator.py b/src/adaptix/_internal/retort/mediator.py
deleted file mode 100644
index a3706ca0..00000000
--- a/src/adaptix/_internal/retort/mediator.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from abc import ABC, abstractmethod
-from typing import Any, Dict, Generic, Iterable, Optional, TypeVar
-
-from ..provider.essential import AggregateCannotProvide, CannotProvide, Mediator, Request
-from ..utils import add_note
-from .routing import RecipeSearcher
-
-T = TypeVar("T")
-
-
-class RecursionResolver(ABC, Generic[T]):
-    @abstractmethod
-    def track_recursion(self, request: Request[T]) -> Optional[Any]:
-        ...
-
-    @abstractmethod
-    def process_request_result(self, request: Request[T], result: T) -> None:
-        ...
-
-
-E = TypeVar("E", bound=Exception)
-
-
-class ErrorRepresentor(ABC):
-    @abstractmethod
-    def get_no_provider_description(self, request: Request) -> str:
-        ...
-
-    @abstractmethod
-    def get_request_context_notes(self, request: Request) -> Iterable[str]:
-        ...
-
-
-class BuiltinMediator(Mediator):
-    def __init__(
-        self,
-        searcher: RecipeSearcher,
-        recursion_resolver: RecursionResolver,
-        error_representor: ErrorRepresentor,
-    ):
-        self.searcher = searcher
-        self.recursion_resolver = recursion_resolver
-        self.error_representor = error_representor
-
-        self._current_request: Optional[Request] = None
-        self.next_offset = 0
-        self.recursion_stubs: Dict[Request, Any] = {}
-
-    def provide(self, request: Request[T]) -> T:
-        stub = self.recursion_resolver.track_recursion(request)
-        if stub is not None:
-            return stub
-
-        self._current_request = request
-        try:
-            result = self._provide_non_recursive(request, 0)
-        finally:
-            self._current_request = None
-
-        self.recursion_resolver.process_request_result(request, result)
-        return result
-
-    def provide_from_next(self) -> Any:
-        if self._current_request is None:
-            raise ValueError
-        return self._provide_non_recursive(self._current_request, self.next_offset)
-
-    def _provide_non_recursive(self, request: Request[T], search_offset: int) -> T:
-        init_next_offset = self.next_offset
-        exceptions = []
-        for provide_callable, next_offset in self.searcher.search_candidates(
-            search_offset, request,
-        ):
-            self.next_offset = next_offset
-            try:
-                result = provide_callable(self, request)
-            except CannotProvide as e:
-                if e.is_terminal:
-                    self.next_offset = init_next_offset
-                    raise self._attach_request_context_note(e, request)
-                exceptions.append(e)
-                continue
-
-            self.next_offset = init_next_offset
-            return result
-
-        raise self._attach_request_context_note(
-            AggregateCannotProvide.make(
-                self.error_representor.get_no_provider_description(request),
-                exceptions,
-                is_demonstrative=True,
-            ),
-            request,
-        )
-
-    def _attach_request_context_note(self, exc: E, request: Request) -> E:
-        notes = self.error_representor.get_request_context_notes(request)
-        for note in notes:
-            add_note(exc, note)
-        return exc
diff --git a/src/adaptix/_internal/retort/operating_retort.py b/src/adaptix/_internal/retort/operating_retort.py
index 03b647ba..c4ecbab7 100644
--- a/src/adaptix/_internal/retort/operating_retort.py
+++ b/src/adaptix/_internal/retort/operating_retort.py
@@ -1,138 +1,146 @@
-from abc import ABC
-from typing import Any, Dict, Iterable, List, Mapping, Optional, Type
+from collections.abc import Iterable, Sequence
+from typing import Any, Callable, Generic, Optional, TypeVar
 
-from ..common import VarTuple
 from ..conversion.request_cls import CoercerRequest, LinkingRequest
+from ..morphing.json_schema.definitions import JSONSchema
+from ..morphing.json_schema.request_cls import InlineJSONSchemaRequest, JSONSchemaRefRequest, JSONSchemaRequest
 from ..morphing.request_cls import DumperRequest, LoaderRequest
-from ..provider.essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
+from ..provider.essential import Mediator, Provider, Request
+from ..provider.loc_stack_tools import format_loc_stack
+from ..provider.located_request import LocatedRequest, LocatedRequestMethodsProvider
 from ..provider.location import AnyLoc
-from ..provider.request_cls import LocatedRequest, LocStack, format_loc_stack
-from ..utils import add_note, copy_exception_dunders, with_module
-from .base_retort import BaseRetort
-from .mediator import ErrorRepresentor, RecursionResolver, T
+from ..provider.methods_provider import method_handler
+from .request_bus import ErrorRepresentor, RecursionResolver, RequestRouter
+from .routers import CheckerAndHandler, SimpleRouter, create_router_for_located_request
+from .searching_retort import SearchingRetort
 
 
 class FuncWrapper:
-    __slots__ = ("__call__",)
+    __slots__ = ("__call__", "_key")
 
-    def __init__(self):
+    def __init__(self, key):
+        self._key = key
         self.__call__ = None
 
     def set_func(self, func):
-        self.__call__ = func.__call__
+        self.__call__ = func
 
+    def __eq__(self, other):
+        if isinstance(other, FuncWrapper):
+            return self._key == other._key
+        return NotImplemented
 
-class MorphingRecursionResolver(RecursionResolver):
-    REQUEST_CLASSES: VarTuple[Type[LocatedRequest]] = (LoaderRequest, DumperRequest)
+    def __hash__(self):
+        return hash(self._key)
 
-    def __init__(self) -> None:
-        self._loc_to_stub: Dict[AnyLoc, FuncWrapper] = {}
 
-    def track_recursion(self, request: Request[T]) -> Optional[Any]:
-        if not isinstance(request, self.REQUEST_CLASSES):
-            return None
+CallableT = TypeVar("CallableT", bound=Callable)
+
+
+class LocatedRequestCallableRecursionResolver(RecursionResolver[LocatedRequest, CallableT], Generic[CallableT]):
+    def __init__(self) -> None:
+        self._loc_to_stub: dict[AnyLoc, FuncWrapper] = {}
 
-        if request.loc_stack.count(request.last_loc) == 1:
+    def track_request(self, request: LocatedRequest) -> Optional[Any]:
+        last_loc = request.last_loc
+        if sum(loc == last_loc for loc in request.loc_stack) == 1:
             return None
 
-        stub = FuncWrapper()
-        self._loc_to_stub[request.last_loc] = stub
+        if last_loc in self._loc_to_stub:
+            return self._loc_to_stub[last_loc]
+        stub = FuncWrapper(last_loc)
+        self._loc_to_stub[last_loc] = stub
         return stub
 
-    def process_request_result(self, request: Request[T], result: T) -> None:
-        if isinstance(request, self.REQUEST_CLASSES) and request.last_loc in self._loc_to_stub:
-            self._loc_to_stub.pop(request.last_loc).set_func(result)
-
+    def track_response(self, request: LocatedRequest, response: CallableT) -> None:
+        last_loc = request.last_loc
+        if last_loc in self._loc_to_stub:
+            self._loc_to_stub.pop(last_loc).set_func(response)
 
-@with_module("adaptix")
-class ProviderNotFoundError(Exception):
-    def __init__(self, message: str):
-        self.message = message
 
-    def __str__(self):
-        return self.message
+RequestT = TypeVar("RequestT", bound=Request)
+LocatedRequestT = TypeVar("LocatedRequestT", bound=LocatedRequest)
 
 
-class BuiltinErrorRepresentor(ErrorRepresentor):
-    _NO_PROVIDER_DESCRIPTION_METHOD: Mapping[Type[Request], str] = {
-        LinkingRequest: "_get_linking_request_description",
-    }
-    _NO_PROVIDER_DESCRIPTION_CONST: Mapping[Type[Request], str] = {
-        LoaderRequest: "Cannot find loader",
-        DumperRequest: "Cannot find dumper",
-        CoercerRequest: "Cannot find coercer",
-    }
+class BaseRequestErrorRepresentor(ErrorRepresentor[RequestT], Generic[RequestT]):
+    def __init__(self, not_found_desc: str):
+        self._not_found_desc = not_found_desc
 
-    def _get_linking_request_description(self, request: LinkingRequest) -> str:
-        dst_desc = self._get_loc_stack_desc(request.destination)
-        return f"Cannot find paired field of `{dst_desc}` for linking"
+    def get_request_context_notes(self, request: RequestT) -> Iterable[str]:
+        return ()
 
-    def get_no_provider_description(self, request: Request) -> str:
-        request_cls = type(request)
-        if request_cls in self._NO_PROVIDER_DESCRIPTION_METHOD:
-            return getattr(self, self._NO_PROVIDER_DESCRIPTION_METHOD[request_cls])(request)
-        if request_cls in self._NO_PROVIDER_DESCRIPTION_CONST:
-            return self._NO_PROVIDER_DESCRIPTION_CONST[request_cls]
-        return f"There is no provider that can process {request}"
+    def get_provider_not_found_description(self, request: RequestT) -> str:
+        return self._not_found_desc
 
-    def _get_loc_stack_desc(self, loc_stack: LocStack[AnyLoc]) -> str:
-        return format_loc_stack(loc_stack)
 
-    def _get_located_request_context_notes(self, request: LocatedRequest) -> Iterable[str]:
-        loc_stack_desc = self._get_loc_stack_desc(request.loc_stack)
+class LocatedRequestErrorRepresentor(BaseRequestErrorRepresentor[LocatedRequestT], Generic[LocatedRequestT]):
+    def get_request_context_notes(self, request: LocatedRequestT) -> Iterable[str]:
+        loc_stack_desc = format_loc_stack(request.loc_stack)
         yield f"Location: `{loc_stack_desc}`"
 
-    def _get_coercer_request_context_notes(self, request: CoercerRequest) -> Iterable[str]:
-        src_desc = self._get_loc_stack_desc(request.src)
-        dst_desc = self._get_loc_stack_desc(request.dst)
-        yield f"Linking: `{src_desc} => {dst_desc}`"
 
-    def get_request_context_notes(self, request: Request) -> Iterable[str]:
-        if isinstance(request, LocatedRequest):
-            yield from self._get_located_request_context_notes(request)
-        elif isinstance(request, CoercerRequest):
-            yield from self._get_coercer_request_context_notes(request)
+class LinkingRequestErrorRepresentor(ErrorRepresentor[LinkingRequest]):
+    def get_request_context_notes(self, request: RequestT) -> Iterable[str]:
+        return ()
 
+    def get_provider_not_found_description(self, request: LinkingRequest) -> str:
+        dst_desc = format_loc_stack(request.destination)
+        return f"Cannot find paired field of `{dst_desc}` for linking"
 
-class OperatingRetort(BaseRetort, Provider, ABC):
-    """A retort that can operate as Retort but have no predefined providers and no high-level user interface"""
 
-    def apply_provider(self, mediator: Mediator, request: Request[T]) -> T:
-        return self._provide_from_recipe(request)
-
-    def _facade_provide(self, request: Request[T], *, error_message: str) -> T:
-        try:
-            return self._provide_from_recipe(request)
-        except CannotProvide as e:
-            cause = self._get_exception_cause(e)
-            exception = ProviderNotFoundError(error_message)
-            if cause is not None:
-                add_note(exception, "Note: The attached exception above contains verbose description of the problem")
-            raise exception from cause
-
-    def _get_exception_cause(self, exc: CannotProvide) -> Optional[CannotProvide]:
-        if isinstance(exc, AggregateCannotProvide):
-            return self._extract_demonstrative_exc(exc)
-        return exc if exc.is_demonstrative else None
-
-    def _extract_demonstrative_exc(self, exc: AggregateCannotProvide) -> Optional[CannotProvide]:
-        demonstrative_exc_list: List[CannotProvide] = []
-        for sub_exc in exc.exceptions:
-            if isinstance(sub_exc, AggregateCannotProvide):
-                sub_exc = self._extract_demonstrative_exc(sub_exc)  # type: ignore[assignment]  # noqa: PLW2901
-                if sub_exc is not None:
-                    demonstrative_exc_list.append(sub_exc)
-            elif sub_exc.is_demonstrative:  # type: ignore[union-attr]
-                demonstrative_exc_list.append(sub_exc)  # type: ignore[arg-type]
-
-        if not exc.is_demonstrative and not demonstrative_exc_list:
-            return None
-        new_exc = exc.derive_upcasting(demonstrative_exc_list)
-        copy_exception_dunders(source=exc, target=new_exc)
-        return new_exc
+class CoercerRequestErrorRepresentor(BaseRequestErrorRepresentor[CoercerRequest]):
+    def get_request_context_notes(self, request: CoercerRequest) -> Iterable[str]:
+        src_desc = format_loc_stack(request.src)
+        dst_desc = format_loc_stack(request.dst)
+        yield f"Linking: `{src_desc} => {dst_desc}`"
 
-    def _create_recursion_resolver(self) -> RecursionResolver:
-        return MorphingRecursionResolver()
 
-    def _get_error_representor(self) -> ErrorRepresentor:
-        return BuiltinErrorRepresentor()
+class JSONSchemaMiddlewareProvider(LocatedRequestMethodsProvider):
+    @method_handler
+    def provide_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
+        loc_stack = request.loc_stack
+        ctx = request.ctx
+        json_schema = mediator.provide_from_next()
+        inline = mediator.mandatory_provide(InlineJSONSchemaRequest(loc_stack=loc_stack, ctx=ctx))
+        if inline:
+            return json_schema
+        ref = mediator.mandatory_provide(JSONSchemaRefRequest(loc_stack=loc_stack, json_schema=json_schema, ctx=ctx))
+        return JSONSchema(ref=ref)
+
+
+class OperatingRetort(SearchingRetort):
+    """A retort that can operate as Retort but have no predefined providers and no high-level user interface"""
+
+    def _get_recipe_head(self) -> Sequence[Provider]:
+        return (
+            JSONSchemaMiddlewareProvider(),
+        )
+
+    def _create_router(
+        self,
+        request_cls: type[RequestT],
+        checkers_and_handlers: Sequence[CheckerAndHandler],
+    ) -> RequestRouter[RequestT]:
+        if issubclass(request_cls, LocatedRequest):
+            return create_router_for_located_request(checkers_and_handlers)  # type: ignore[return-value]
+        return SimpleRouter(checkers_and_handlers)
+
+    def _create_error_representor(self, request_cls: type[RequestT]) -> ErrorRepresentor[RequestT]:
+        if issubclass(request_cls, LoaderRequest):
+            return LocatedRequestErrorRepresentor("Cannot find loader")
+        if issubclass(request_cls, DumperRequest):
+            return LocatedRequestErrorRepresentor("Cannot find dumper")
+        if issubclass(request_cls, LocatedRequest):
+            return LocatedRequestErrorRepresentor(f"Can not satisfy {request_cls}")
+
+        if issubclass(request_cls, CoercerRequest):
+            return CoercerRequestErrorRepresentor("Cannot find coercer")  # type: ignore[return-value]
+        if issubclass(request_cls, LinkingRequest):
+            return LinkingRequestErrorRepresentor()  # type: ignore[return-value]
+
+        return BaseRequestErrorRepresentor(f"Can not satisfy {request_cls}")
+
+    def _create_recursion_resolver(self, request_cls: type[RequestT]) -> Optional[RecursionResolver[RequestT, Any]]:
+        if issubclass(request_cls, (LoaderRequest, DumperRequest)):
+            return LocatedRequestCallableRecursionResolver()  # type: ignore[return-value]
+        return None
diff --git a/src/adaptix/_internal/retort/request_bus.py b/src/adaptix/_internal/retort/request_bus.py
new file mode 100644
index 00000000..25f4ce7f
--- /dev/null
+++ b/src/adaptix/_internal/retort/request_bus.py
@@ -0,0 +1,145 @@
+from abc import ABC, abstractmethod
+from collections.abc import Iterable
+from typing import Any, Callable, Generic, Optional, TypeVar
+
+from ..provider.essential import (
+    AggregateCannotProvide,
+    CannotProvide,
+    DirectMediator,
+    Mediator,
+    Request,
+    RequestHandler,
+)
+from ..utils import add_note
+from .builtin_mediator import RequestBus
+
+RequestT = TypeVar("RequestT", bound=Request)
+ResponseT = TypeVar("ResponseT")
+
+
+class ErrorRepresentor(ABC, Generic[RequestT]):
+    @abstractmethod
+    def get_provider_not_found_description(self, request: RequestT) -> str:
+        ...
+
+    @abstractmethod
+    def get_request_context_notes(self, request: RequestT) -> Iterable[str]:
+        ...
+
+
+class RequestRouter(ABC, Generic[RequestT]):
+    """An offset of each element must belong to [0; max_offset)"""
+
+    @abstractmethod
+    def route_handler(
+        self,
+        mediator: DirectMediator,
+        request: RequestT,
+        search_offset: int,
+    ) -> tuple[RequestHandler, int]:
+        """
+        :raises: StopIteration
+        """
+
+    @abstractmethod
+    def get_max_offset(self) -> int:
+        ...
+
+
+E = TypeVar("E", bound=Exception)
+
+
+class BasicRequestBus(RequestBus[RequestT, ResponseT], Generic[RequestT, ResponseT]):
+    __slots__ = ("_router", "_error_representor", "_mediator_factory")
+
+    def __init__(
+        self,
+        router: RequestRouter[RequestT],
+        error_representor: ErrorRepresentor[RequestT],
+        mediator_factory: Callable[[Request, int], Mediator],
+    ):
+        self._router = router
+        self._error_representor = error_representor
+        self._mediator_factory = mediator_factory
+
+    def send(self, request: RequestT) -> Any:
+        return self._send_inner(request, 0)
+
+    def send_chaining(self, request: RequestT, search_offset: int) -> Any:
+        return self._send_inner(request, search_offset)
+
+    def _send_inner(self, request: RequestT, search_offset: int) -> Any:
+        exceptions: list[CannotProvide] = []
+        next_offset = search_offset
+        mediator = self._mediator_factory(request, next_offset)
+        while True:
+            try:
+                handler, next_offset = self._router.route_handler(mediator, request, next_offset)
+            except StopIteration:
+                exc = AggregateCannotProvide.make(
+                    self._error_representor.get_provider_not_found_description(request),
+                    exceptions,
+                    is_demonstrative=True,
+                )
+                self._attach_request_context_notes(exc, request)
+                self._attach_sub_exceptions_notes(exc, exceptions)
+                raise exc from None
+            except CannotProvide:
+                raise RuntimeError("RequestChecker raises CannotProvide")
+
+            mediator = self._mediator_factory(request, next_offset)
+            try:
+                response = handler(mediator, request)
+            except CannotProvide as e:
+                if e.is_terminal:
+                    raise self._attach_request_context_notes(e, request)
+                exceptions.append(e)
+                continue
+
+            return response
+
+    def _attach_request_context_notes(self, exc: E, request: RequestT) -> E:
+        notes = self._error_representor.get_request_context_notes(request)
+        for note in notes:
+            add_note(exc, note)
+        return exc
+
+    def _attach_sub_exceptions_notes(self, exc: E, sub_exceptions: Iterable[CannotProvide]) -> E:
+        for sub_exc in sub_exceptions:
+            if sub_exc.parent_notes_gen is not None:
+                for note in sub_exc.parent_notes_gen():
+                    add_note(exc, note)
+        return exc
+
+
+class RecursionResolver(ABC, Generic[RequestT, ResponseT]):
+    @abstractmethod
+    def track_request(self, request: RequestT) -> Optional[ResponseT]:
+        ...
+
+    @abstractmethod
+    def track_response(self, request: RequestT, response: ResponseT) -> None:
+        ...
+
+
+class RecursiveRequestBus(BasicRequestBus[RequestT, ResponseT], Generic[RequestT, ResponseT]):
+    __slots__ = (*BasicRequestBus.__slots__, "_recursion_resolver")
+
+    def __init__(
+        self,
+        router: RequestRouter[RequestT],
+        error_representor: ErrorRepresentor[RequestT],
+        mediator_factory: Callable[[Request, int], Mediator],
+        recursion_resolver: RecursionResolver[RequestT, ResponseT],
+    ):
+        super().__init__(router, error_representor, mediator_factory)
+        self._recursion_resolver = recursion_resolver
+
+    def send(self, request: RequestT) -> Any:
+        stub = self._recursion_resolver.track_request(request)
+        if stub is not None:
+            return stub
+
+        result = self._send_inner(request, 0)
+        self._recursion_resolver.track_response(request, result)
+        return result
diff --git a/src/adaptix/_internal/retort/routers.py b/src/adaptix/_internal/retort/routers.py
new file mode 100644
index 00000000..e5752de9
--- /dev/null
+++ b/src/adaptix/_internal/retort/routers.py
@@ -0,0 +1,121 @@
+from collections.abc import Sequence
+from itertools import islice
+from typing import Optional, TypeVar, Union
+
+from ..common import TypeHint
+from ..provider.essential import DirectMediator, Request, RequestChecker, RequestHandler
+from ..provider.loc_stack_filtering import ExactOriginLSC
+from ..provider.located_request import LocatedRequest, LocatedRequestChecker
+from ..type_tools import normalize_type
+from .request_bus import RequestRouter
+
+RequestT = TypeVar("RequestT", bound=Request)
+CheckerAndHandler = tuple[RequestChecker, RequestHandler]
+
+
+class SimpleRouter(RequestRouter[RequestT]):
+    __slots__ = ("_checkers_and_handlers", )
+
+    def __init__(self, checkers_and_handlers: Sequence[CheckerAndHandler]):
+        self._checkers_and_handlers = checkers_and_handlers
+
+    def route_handler(
+        self,
+        mediator: DirectMediator,
+        request: RequestT,
+        search_offset: int,
+    ) -> tuple[RequestHandler, int]:
+        for i, (checker, handler) in enumerate(
+            islice(self._checkers_and_handlers, search_offset, None),
+            start=search_offset,
+        ):
+            if checker.check_request(mediator, request):
+                return handler, i + 1
+        raise StopIteration
+
+    def get_max_offset(self) -> int:
+        return len(self._checkers_and_handlers)
+
+
+OriginToHandler = dict[TypeHint, RequestHandler]
+LRRoutingItem = Union[CheckerAndHandler, OriginToHandler]
+
+
+class LocatedRequestRouter(RequestRouter[LocatedRequest]):
+    __slots__ = ("_items", )
+
+    def __init__(self, items: Sequence[Union[CheckerAndHandler, OriginToHandler]]):
+        self._items = items
+
+    def route_handler(
+        self,
+        mediator: DirectMediator,
+        request: LocatedRequest,
+        search_offset: int,
+    ) -> tuple[RequestHandler, int]:
+        try:
+            origin = normalize_type(request.last_loc.type).origin
+        except ValueError:
+            origin = object()
+
+        for i, routing_item in enumerate(
+            islice(self._items, search_offset, None),
+            start=search_offset,
+        ):
+            if type(routing_item) is tuple:
+                if routing_item[0].check_request(mediator, request):
+                    return routing_item[1], i + 1
+            else:
+                handler = routing_item.get(origin)  # type: ignore[union-attr]
+                if handler is not None:
+                    return handler, i + 1
+        raise StopIteration
+
+    def get_max_offset(self) -> int:
+        return len(self._items)
+
+
+class ExactOriginCombiner:
+    def __init__(self) -> None:
+        self._combo: OriginToHandler = {}
+
+    def _stop_combo(self, checker_and_handler: Optional[CheckerAndHandler]) -> Sequence[LRRoutingItem]:
+        result: list[LRRoutingItem] = []
+        if self._combo:
+            if len(self._combo) == 1:
+                [(origin, handler)] = self._combo.items()
+                result.append((LocatedRequestChecker(ExactOriginLSC(origin)), handler))
+            else:
+                result.append(self._combo)
+                self._combo = {}
+
+        if checker_and_handler is not None:
+            result.append(checker_and_handler)
+        return result
+
+    def register_item(self, checker_and_handler: CheckerAndHandler) -> Sequence[LRRoutingItem]:
+        checker, handler = checker_and_handler
+        if isinstance(checker, LocatedRequestChecker) and isinstance(checker.loc_stack_checker, ExactOriginLSC):
+            origin = checker.loc_stack_checker.origin
+            if origin in self._combo:
+                return self._stop_combo(checker_and_handler)
+            self._combo[origin] = handler
+            return []
+
+        return self._stop_combo(checker_and_handler)
+
+    def finalize(self) -> Sequence[LRRoutingItem]:
+        return self._stop_combo(None)
+
+
+def create_router_for_located_request(
+    checkers_and_handlers: Sequence[CheckerAndHandler],
+) -> RequestRouter[LocatedRequest]:
+    items: list[Union[CheckerAndHandler, OriginToHandler]] = []
+
+    combiner = ExactOriginCombiner()
+    for checkers_and_handler in checkers_and_handlers:
+        items.extend(combiner.register_item(checkers_and_handler))
+    items.extend(combiner.finalize())
+
+    return LocatedRequestRouter(items)
diff --git a/src/adaptix/_internal/retort/routing.py b/src/adaptix/_internal/retort/routing.py
deleted file mode 100644
index cbeebd32..00000000
--- a/src/adaptix/_internal/retort/routing.py
+++ /dev/null
@@ -1,177 +0,0 @@
-from abc import ABC, abstractmethod
-from itertools import islice
-from typing import Callable, Dict, Iterable, List, Sequence, Set, Tuple, Type, TypeVar
-
-from ..common import TypeHint
-from ..provider.essential import CannotProvide, Mediator, Provider, Request
-from ..provider.loc_stack_filtering import ExactOriginLSC
-from ..provider.provider_wrapper import ProviderWithLSC, RequestClassDeterminedProvider
-from ..provider.request_cls import LocatedRequest, TypeHintLoc, try_normalize_type
-
-T = TypeVar("T")
-ProvideCallable = Callable[[Mediator, Request[T]], T]
-SearchResult = Tuple[ProvideCallable[T], int]
-
-
-class RecipeSearcher(ABC):
-    """An object that implements iterating over recipe list.
-
-    An offset of each element must belong to [0; max_offset)
-    """
-
-    @abstractmethod
-    def search_candidates(self, search_offset: int, request: Request[T]) -> Iterable[SearchResult[T]]:
-        ...
-
-    @abstractmethod
-    def get_max_offset(self) -> int:
-        ...
-
-    @abstractmethod
-    def clear_cache(self):
-        ...
-
-
-class RawRecipeSearcher(RecipeSearcher):
-    def __init__(self, recipe: Sequence[Provider]):
-        self.recipe = recipe
-
-    def search_candidates(self, search_offset: int, request: Request) -> Iterable[SearchResult]:
-        for i, provider in enumerate(
-            islice(self.recipe, search_offset, None),
-            start=search_offset,
-        ):
-            yield provider.apply_provider, i + 1
-
-    def clear_cache(self):
-        pass
-
-    def get_max_offset(self) -> int:
-        return len(self.recipe)
-
-
-class Combiner(ABC):
-    @abstractmethod
-    def add_element(self, provider: Provider) -> bool:
-        ...
-
-    @abstractmethod
-    def combine_elements(self) -> Sequence[Provider]:
-        ...
-
-    @abstractmethod
-    def has_elements(self) -> bool:
-        ...
-
-
-class ExactOriginCombiner(Combiner):
-    def __init__(self) -> None:
-        self._combo: List[Tuple[ExactOriginLSC, Provider]] = []
-        self._origins: Set[TypeHint] = set()
-
-    def add_element(self, provider: Provider) -> bool:
-        if not isinstance(provider, ProviderWithLSC):
-            return False
-        loc_stack_checker = provider.get_loc_stack_checker()
-        if not isinstance(loc_stack_checker, ExactOriginLSC):
-            return False
-        if loc_stack_checker.origin in self._origins:
-            return False
-
-        self._combo.append((loc_stack_checker, provider))
-        self._origins.add(loc_stack_checker.origin)
-        return True
-
-    def combine_elements(self) -> Sequence[Provider]:
-        if len(self._combo) == 1:
-            element = self._combo[0][1]
-            self._combo.clear()
-            self._origins.clear()
-            return [element]
-
-        merged_provider = ExactOriginMergedProvider(self._combo)
-        self._combo.clear()
-        self._origins.clear()
-        return [merged_provider]
-
-    def has_elements(self) -> bool:
-        return bool(self._combo)
-
-
-class ExactOriginMergedProvider(Provider):
-    def __init__(self, origins_to_providers: Sequence[Tuple[ExactOriginLSC, Provider]]):
-        self.origin_to_provider = {
-            loc_stack_checker.origin: provider
-            for loc_stack_checker, provider in reversed(origins_to_providers)
-        }
-
-    def apply_provider(self, mediator: Mediator[T], request: Request[T]) -> T:
-        if not isinstance(request, LocatedRequest):
-            raise CannotProvide(f"Request must be instance of {LocatedRequest}")
-
-        loc = request.last_loc.cast_or_raise(
-            TypeHintLoc,
-            lambda: CannotProvide(f"Request location must be instance of {TypeHintLoc}"),
-        )
-        norm = try_normalize_type(loc.type)
-        try:
-            provider = self.origin_to_provider[norm.origin]
-        except KeyError:
-            raise CannotProvide from None
-
-        return provider.apply_provider(mediator, request)
-
-
-class IntrospectingRecipeSearcher(RecipeSearcher):
-    def __init__(self, recipe: Sequence[Provider]):
-        self._recipe = recipe
-        self._cls_to_recipe: Dict[Type[Request], Sequence[Provider]] = {}
-
-    def search_candidates(self, search_offset: int, request: Request) -> Iterable[SearchResult]:
-        request_cls = type(request)
-        try:
-            sub_recipe = self._cls_to_recipe[request_cls]
-        except KeyError:
-            sub_recipe = self._collect_candidates(request_cls, self._recipe)
-            self._cls_to_recipe[request_cls] = sub_recipe
-
-        for i, provider in enumerate(
-            islice(sub_recipe, search_offset, None),
-            start=search_offset,
-        ):
-            yield provider.apply_provider, i + 1
-
-    def _create_combiner(self) -> Combiner:
-        return ExactOriginCombiner()
-
-    def _merge_providers(self, recipe: Sequence[Provider]) -> Sequence[Provider]:
-        combiner = self._create_combiner()
-
-        result: List[Provider] = []
-        for provider in recipe:
-            is_added = combiner.add_element(provider)
-            if not is_added:
-                if combiner.has_elements():
-                    result.extend(combiner.combine_elements())
-                result.append(provider)
-
-        if combiner.has_elements():
-            result.extend(combiner.combine_elements())
-        return result
-
-    def _collect_candidates(self, request_cls: Type[Request], recipe: Sequence[Provider]) -> Sequence[Provider]:
-        candidates = [
-            provider
-            for provider in recipe
-            if (
-                not isinstance(provider, RequestClassDeterminedProvider)
-                or provider.maybe_can_process_request_cls(request_cls)
-            )
-        ]
-        return self._merge_providers(candidates)
-
-    def clear_cache(self):
-        self._cls_to_recipe = {}
-
-    def get_max_offset(self) -> int:
-        return len(self._recipe)
diff --git a/src/adaptix/_internal/retort/searching_retort.py b/src/adaptix/_internal/retort/searching_retort.py
new file mode 100644
index 00000000..a552e750
--- /dev/null
+++ b/src/adaptix/_internal/retort/searching_retort.py
@@ -0,0 +1,186 @@
+from abc import ABC, abstractmethod
+from collections import defaultdict
+from collections.abc import Iterable, Mapping, Sequence
+from typing import Any, Callable, Optional, TypeVar
+
+from ..compat import CompatBaseExceptionGroup
+from ..provider.essential import (
+    AggregateCannotProvide,
+    CannotProvide,
+    Mediator,
+    Provider,
+    Request,
+    RequestChecker,
+    RequestHandler,
+)
+from ..provider.request_checkers import AlwaysTrueRequestChecker
+from ..utils import add_note, copy_exception_dunders, with_module
+from .base_retort import BaseRetort
+from .builtin_mediator import BuiltinMediator, RequestBus
+from .request_bus import BasicRequestBus, ErrorRepresentor, RecursionResolver, RecursiveRequestBus, RequestRouter
+from .routers import CheckerAndHandler
+
+
+@with_module("adaptix")
+class ProviderNotFoundError(Exception):
+    def __init__(self, message: str):
+        self.message = message
+
+    def __str__(self):
+        return self.message
+
+
+T = TypeVar("T")
+RequestT = TypeVar("RequestT", bound=Request)
+
+
+class SearchingRetort(BaseRetort, Provider, ABC):
+    """A retort that can operate as Retort but have no predefined providers and no high-level user interface"""
+
+    def __init__(self, *, recipe: Iterable[Provider] = (), hide_traceback: bool = True):
+        self._hide_traceback = hide_traceback
+        super().__init__(recipe=recipe)
+
+    def _provide_from_recipe(self, request: Request[T]) -> T:
+        return self._create_mediator(request).provide(request)
+
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        def retort_request_handler(mediator, request):
+            return self._provide_from_recipe(request)
+
+        request_classes = {
+            request_cls
+            for provider in self._get_full_recipe()
+            for request_cls, checker, handler in provider.get_request_handlers()
+        }
+        return [
+            (request_class, AlwaysTrueRequestChecker(), retort_request_handler)
+            for request_class in request_classes
+        ]
+
+    def _exception_walk(self, exc: BaseException) -> Iterable[BaseException]:
+        yield exc
+        if isinstance(exc, CompatBaseExceptionGroup):
+            for sub_exc in exc.exceptions:
+                yield from self._exception_walk(sub_exc)
+        if exc.__cause__ is not None:
+            yield from self._exception_walk(exc.__cause__)
+        if exc.__context__ is not None:
+            yield from self._exception_walk(exc.__context__)
+
+    def _facade_provide(self, request: Request[T], *, error_message: str) -> T:
+        try:
+            return self._provide_from_recipe(request)
+        except CannotProvide as e:
+            exception = ProviderNotFoundError(error_message)
+
+            cause = self._get_exception_cause(e)
+            if cause is not None:
+                for sub_exc in self._exception_walk(cause):
+                    sub_exc.__traceback__ = None
+                add_note(exception, "Note: The attached exception above contains verbose description of the problem")
+            raise exception from cause
+
+    def _get_exception_cause(self, exc: CannotProvide) -> Optional[CannotProvide]:
+        if isinstance(exc, AggregateCannotProvide):
+            return self._extract_demonstrative_exc(exc)
+        return exc if exc.is_demonstrative else None
+
+    def _extract_demonstrative_exc(self, exc: AggregateCannotProvide) -> Optional[CannotProvide]:
+        demonstrative_exc_list: list[CannotProvide] = []
+        for sub_exc in exc.exceptions:
+            if isinstance(sub_exc, AggregateCannotProvide):
+                sub_exc = self._extract_demonstrative_exc(sub_exc)  # type: ignore[assignment]  # noqa: PLW2901
+                if sub_exc is not None:
+                    demonstrative_exc_list.append(sub_exc)
+            elif sub_exc.is_demonstrative:  # type: ignore[union-attr]
+                demonstrative_exc_list.append(sub_exc)  # type: ignore[arg-type]
+
+        if not exc.is_demonstrative and not demonstrative_exc_list:
+            return None
+        new_exc = exc.derive_upcasting(demonstrative_exc_list)
+        copy_exception_dunders(source=exc, target=new_exc)
+        return new_exc
+
+    def _calculate_derived(self) -> None:
+        super()._calculate_derived()
+        self._request_cls_to_router = self._create_request_cls_to_router(self._full_recipe)
+        self._request_cls_to_error_representor = {
+            request_cls: self._create_error_representor(request_cls)
+            for request_cls in self._request_cls_to_router
+        }
+        self._call_cache: dict[Any, Any] = {}
+
+    def _create_request_cls_to_router(self, full_recipe: Sequence[Provider]) -> Mapping[type[Request], RequestRouter]:
+        request_cls_to_checkers_and_handlers: defaultdict[type[Request], list[CheckerAndHandler]] = defaultdict(list)
+        for provider in full_recipe:
+            for request_cls, checker, handler in provider.get_request_handlers():
+                request_cls_to_checkers_and_handlers[request_cls].append((checker, handler))
+
+        return {
+            request_cls: self._create_router(request_cls, checkers_and_handlers)
+            for request_cls, checkers_and_handlers in request_cls_to_checkers_and_handlers.items()
+        }
+
+    @abstractmethod
+    def _create_router(
+        self,
+        request_cls: type[RequestT],
+        checkers_and_handlers: Sequence[CheckerAndHandler],
+    ) -> RequestRouter[RequestT]:
+        ...
+
+    @abstractmethod
+    def _create_error_representor(self, request_cls: type[RequestT]) -> ErrorRepresentor[RequestT]:
+        ...
+
+    @abstractmethod
+    def _create_recursion_resolver(self, request_cls: type[RequestT]) -> Optional[RecursionResolver[RequestT, Any]]:
+        ...
+
+    def _create_request_bus(
+        self,
+        request_cls: type[RequestT],
+        router: RequestRouter[RequestT],
+        mediator_factory: Callable[[Request, int], Mediator],
+    ) -> RequestBus:
+        error_representor = self._request_cls_to_error_representor[request_cls]
+        recursion_resolver = self._create_recursion_resolver(request_cls)
+        if recursion_resolver is not None:
+            return RecursiveRequestBus(
+                router=router,
+                error_representor=error_representor,
+                mediator_factory=mediator_factory,
+                recursion_resolver=recursion_resolver,
+            )
+        return BasicRequestBus(
+            router=router,
+            error_representor=error_representor,
+            mediator_factory=mediator_factory,
+        )
+
+    def _create_no_request_bus_error_maker(self) -> Callable[[Request], CannotProvide]:
+        def no_request_bus_error_maker(request: Request) -> CannotProvide:
+            return CannotProvide(f"Can not satisfy {type(request)}")
+
+        return no_request_bus_error_maker
+
+    def _create_mediator(self, init_request: Request[T]) -> Mediator[T]:
+        request_buses: Mapping[type[Request], RequestBus]
+        no_request_bus_error_maker = self._create_no_request_bus_error_maker()
+        call_cache = self._call_cache
+
+        def mediator_factory(request, search_offset):
+            return BuiltinMediator(
+                request_buses=request_buses,
+                request=request,
+                search_offset=search_offset,
+                no_request_bus_error_maker=no_request_bus_error_maker,
+                call_cache=call_cache,
+            )
+
+        request_buses = {
+            request_cls: self._create_request_bus(request_cls, router, mediator_factory)
+            for request_cls, router in self._request_cls_to_router.items()
+        }
+        return mediator_factory(init_request, 0)
diff --git a/src/adaptix/_internal/struct_trail.py b/src/adaptix/_internal/struct_trail.py
index f2983723..f98d33d2 100644
--- a/src/adaptix/_internal/struct_trail.py
+++ b/src/adaptix/_internal/struct_trail.py
@@ -1,6 +1,7 @@
 from collections import deque
+from collections.abc import Reversible, Sequence
 from dataclasses import dataclass
-from typing import Any, Reversible, Sequence, TypeVar, Union
+from typing import Any, TypeVar, Union
 
 from .feature_requirement import HAS_NATIVE_EXC_GROUP
 
diff --git a/src/adaptix/_internal/type_tools/__init__.py b/src/adaptix/_internal/type_tools/__init__.py
index 79d25e62..989ebf70 100644
--- a/src/adaptix/_internal/type_tools/__init__.py
+++ b/src/adaptix/_internal/type_tools/__init__.py
@@ -23,3 +23,4 @@
     make_norm_type,
     normalize_type,
 )
+from .type_evaler import exec_type_checking, make_fragments_collector
diff --git a/src/adaptix/_internal/type_tools/basic_utils.py b/src/adaptix/_internal/type_tools/basic_utils.py
index 914da5f4..beedeedb 100644
--- a/src/adaptix/_internal/type_tools/basic_utils.py
+++ b/src/adaptix/_internal/type_tools/basic_utils.py
@@ -1,9 +1,9 @@
 import types
 import typing
-from typing import Any, Dict, ForwardRef, Generic, NewType, Protocol, TypedDict, TypeVar, Union
+from typing import Annotated, Any, ForwardRef, Generic, NewType, Protocol, TypedDict, TypeVar, Union
 
 from ..common import TypeHint, VarTuple
-from ..feature_requirement import HAS_ANNOTATED, HAS_PY_39, HAS_PY_312, HAS_STD_CLASSES_GENERICS
+from ..feature_requirement import HAS_PY_312
 from .constants import BUILTIN_ORIGIN_TO_TYPEVARS
 from .fundamentals import get_generic_args, get_type_vars, strip_alias
 
@@ -81,16 +81,12 @@ def is_generic(tp: TypeHint) -> bool:
         bool(get_type_vars(tp))
         or (
             strip_alias(tp) in BUILTIN_ORIGIN_TO_TYPEVARS
-            and tp != type
+            and tp is not type
             and not is_parametrized(tp)
-            and (
-                bool(HAS_STD_CLASSES_GENERICS) or not isinstance(tp, type)
-            )
         )
         or (
-            bool(HAS_ANNOTATED)
-            and strip_alias(tp) == typing.Annotated
-            and tp != typing.Annotated
+            strip_alias(tp) == Annotated
+            and tp != Annotated
             and is_generic(tp.__origin__)
         )
     )
@@ -129,7 +125,7 @@ def get_type_vars_of_parametrized(tp: TypeHint) -> VarTuple[TypeVar]:
     if not params:
         return ()
     if isinstance(tp, type):
-        if HAS_STD_CLASSES_GENERICS and isinstance(tp, types.GenericAlias):
+        if isinstance(tp, types.GenericAlias):
             return params
         return ()
     if strip_alias(tp) != tp and get_generic_args(tp) == ():
@@ -137,9 +133,5 @@ def get_type_vars_of_parametrized(tp: TypeHint) -> VarTuple[TypeVar]:
     return params
 
 
-if HAS_PY_39:
-    def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        return forward_ref._evaluate(namespace, None, recursive_guard=frozenset())
-else:
-    def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        return forward_ref._evaluate(namespace, None)  # type: ignore[call-arg]
+def eval_forward_ref(namespace: dict[str, Any], forward_ref: ForwardRef):
+    return forward_ref._evaluate(namespace, None, recursive_guard=frozenset())
diff --git a/src/adaptix/_internal/type_tools/constants.py b/src/adaptix/_internal/type_tools/constants.py
index e2963997..b6a13ebf 100644
--- a/src/adaptix/_internal/type_tools/constants.py
+++ b/src/adaptix/_internal/type_tools/constants.py
@@ -3,8 +3,9 @@
 import concurrent.futures
 import queue
 import re
+from collections.abc import Mapping
 from os import PathLike
-from typing import Mapping, TypeVar
+from typing import TypeVar
 
 from ..common import VarTuple
 
diff --git a/src/adaptix/_internal/type_tools/fundamentals.py b/src/adaptix/_internal/type_tools/fundamentals.py
index f83bfc3b..0a403805 100644
--- a/src/adaptix/_internal/type_tools/fundamentals.py
+++ b/src/adaptix/_internal/type_tools/fundamentals.py
@@ -2,7 +2,7 @@
 from typing import TypeVar, get_args, get_origin, get_type_hints
 
 from ..common import TypeHint, VarTuple
-from ..feature_requirement import HAS_ANNOTATED, HAS_SUPPORTED_PYDANTIC_PKG
+from ..feature_requirement import HAS_SUPPORTED_PYDANTIC_PKG
 
 __all__ = ("is_pydantic_class", "strip_alias", "get_type_vars", "get_generic_args", "get_all_type_hints")
 
@@ -41,8 +41,5 @@ def get_generic_args(tp: TypeHint) -> VarTuple[TypeHint]:
     return get_args(tp)
 
 
-if HAS_ANNOTATED:
-    def get_all_type_hints(obj, globalns=None, localns=None):
-        return get_type_hints(obj, globalns, localns, include_extras=True)
-else:
-    get_all_type_hints = get_type_hints
+def get_all_type_hints(obj, globalns=None, localns=None):
+    return get_type_hints(obj, globalns, localns, include_extras=True)
diff --git a/src/adaptix/_internal/type_tools/generic_resolver.py b/src/adaptix/_internal/type_tools/generic_resolver.py
index 90233a31..3f8c7347 100644
--- a/src/adaptix/_internal/type_tools/generic_resolver.py
+++ b/src/adaptix/_internal/type_tools/generic_resolver.py
@@ -1,12 +1,13 @@
 import typing
+from collections.abc import Collection, Hashable, Mapping
 from dataclasses import dataclass, replace
 from itertools import chain
-from typing import Callable, Collection, Dict, Generic, Hashable, Mapping, TypeVar
+from typing import Callable, Generic, TypeVar
 
 from ..common import TypeHint
 from ..feature_requirement import HAS_TV_TUPLE, HAS_UNPACK
-from . import get_generic_args
 from .basic_utils import get_type_vars, get_type_vars_of_parametrized, is_generic, is_parametrized, strip_alias
+from .fundamentals import get_generic_args
 from .implicit_params import fill_implicit_params
 from .normalize_type import normalize_type
 
@@ -85,7 +86,7 @@ def _get_members_by_parents(self, tp) -> MembersStorage[K, M]:
         if not hasattr(tp, "__orig_bases__"):
             return members_storage
 
-        bases_members: Dict[K, TypeHint] = {}
+        bases_members: dict[K, TypeHint] = {}
         for base in reversed(tp.__orig_bases__):
             bases_members.update(self.get_resolved_members(base).members)
 
diff --git a/src/adaptix/_internal/type_tools/implicit_params.py b/src/adaptix/_internal/type_tools/implicit_params.py
index 99f4fff5..34c45e96 100644
--- a/src/adaptix/_internal/type_tools/implicit_params.py
+++ b/src/adaptix/_internal/type_tools/implicit_params.py
@@ -1,6 +1,6 @@
 import sys
 import typing
-from typing import Any, ForwardRef, Tuple, TypeVar
+from typing import Any, ForwardRef, TypeVar
 
 from ..common import TypeHint, VarTuple
 from ..feature_requirement import HAS_PARAM_SPEC, HAS_TV_TUPLE
@@ -18,7 +18,7 @@ def _process_type_var(self, type_var) -> TypeHint:
         if HAS_PARAM_SPEC and isinstance(type_var, typing.ParamSpec):
             return ...
         if HAS_TV_TUPLE and isinstance(type_var, typing.TypeVarTuple):
-            return typing.Unpack[Tuple[Any, ...]]
+            return typing.Unpack[tuple[Any, ...]]
         if type_var.__constraints__:
             return create_union(
                 tuple(
diff --git a/src/adaptix/_internal/type_tools/norm_utils.py b/src/adaptix/_internal/type_tools/norm_utils.py
index 96e52afa..a3aeaa5d 100644
--- a/src/adaptix/_internal/type_tools/norm_utils.py
+++ b/src/adaptix/_internal/type_tools/norm_utils.py
@@ -1,14 +1,11 @@
 import typing
 from dataclasses import InitVar
-from typing import ClassVar, Final, TypeVar
+from typing import Annotated, ClassVar, Final, TypeVar
 
-from ..feature_requirement import HAS_ANNOTATED, HAS_TYPED_DICT_REQUIRED
+from ..feature_requirement import HAS_TYPED_DICT_REQUIRED
 from .normalize_type import BaseNormType
 
-_TYPE_TAGS = [Final, ClassVar, InitVar]
-
-if HAS_ANNOTATED:
-    _TYPE_TAGS.append(typing.Annotated)
+_TYPE_TAGS = [Final, ClassVar, InitVar, Annotated]
 
 if HAS_TYPED_DICT_REQUIRED:
     _TYPE_TAGS.extend([typing.Required, typing.NotRequired])
diff --git a/src/adaptix/_internal/type_tools/normalize_type.py b/src/adaptix/_internal/type_tools/normalize_type.py
index 34e8b142..201dae0c 100644
--- a/src/adaptix/_internal/type_tools/normalize_type.py
+++ b/src/adaptix/_internal/type_tools/normalize_type.py
@@ -1,32 +1,26 @@
-# ruff: noqa: RET503
+# ruff: noqa: RET503, UP006
 import dataclasses
 import sys
 import types
 import typing
 from abc import ABC, abstractmethod
 from collections import abc as c_abc, defaultdict
+from collections.abc import Hashable, Iterable, Sequence
 from copy import copy
 from dataclasses import InitVar, dataclass
 from enum import Enum, EnumMeta
 from functools import lru_cache, partial
 from typing import (
+    Annotated,
     Any,
     Callable,
     ClassVar,
-    DefaultDict,
-    Dict,
     Final,
     ForwardRef,
-    Hashable,
-    Iterable,
-    List,
     Literal,
     NewType,
     NoReturn,
     Optional,
-    Sequence,
-    Tuple,
-    Type,
     TypeVar,
     Union,
     overload,
@@ -34,7 +28,6 @@
 
 from ..common import TypeHint, VarTuple
 from ..feature_requirement import (
-    HAS_ANNOTATED,
     HAS_PARAM_SPEC,
     HAS_PY_310,
     HAS_PY_311,
@@ -175,7 +168,7 @@ def __eq__(self, other):
 class _AnnotatedNormType(_BasicNormType):
     @property
     def origin(self) -> Any:
-        return typing.Annotated
+        return Annotated
 
     __slots__ = (*_BasicNormType.__slots__, "_hash")
 
@@ -243,7 +236,7 @@ def origin(self) -> Any:
         return self._var
 
     @property
-    def args(self) -> Tuple[()]:
+    def args(self) -> tuple[()]:
         return ()
 
     @property
@@ -288,7 +281,7 @@ def origin(self) -> Any:
         return self._var
 
     @property
-    def args(self) -> Tuple[()]:
+    def args(self) -> tuple[()]:
         return ()
 
     @property
@@ -325,7 +318,7 @@ def param_spec(self) -> NormTV:
         return self._param_spec
 
     @property
-    def args(self) -> Tuple[()]:
+    def args(self) -> tuple[()]:
         return ()
 
     @property
@@ -418,7 +411,7 @@ def make_norm_type(
         if not all(type(arg) in [int, bool, str, bytes] or isinstance(type(arg), EnumMeta) for arg in args):
             raise TypeError
         return _LiteralNormType(args, source=source)
-    if HAS_ANNOTATED and origin == typing.Annotated:
+    if origin == Annotated:
         return _AnnotatedNormType(args, source=source)
     if isinstance(origin, TypeVar):
         raise TypeError
@@ -471,7 +464,7 @@ def _replace_source_with_union(norm: BaseNormType, sources: list) -> BaseNormTyp
 NormAspect = Callable[["TypeNormalizer", Any, Any, tuple], Optional[BaseNormType]]
 
 
-class AspectStorage(List[str]):
+class AspectStorage(list[str]):
     @overload
     def add(self, *, condition: object = True) -> Callable[[NormAspect], NormAspect]:
         ...
@@ -503,9 +496,9 @@ class NotSubscribedError(ValueError):
 class TypeNormalizer:
     def __init__(self, implicit_params_getter: ImplicitParamsGetter):
         self.implicit_params_getter = implicit_params_getter
-        self._namespace: Optional[Dict[str, Any]] = None
+        self._namespace: Optional[dict[str, Any]] = None
 
-    def _with_namespace(self: TN, namespace: Dict[str, Any]) -> TN:
+    def _with_namespace(self: TN, namespace: dict[str, Any]) -> TN:
         self_copy = copy(self)
         self_copy._namespace = namespace
         return self_copy
@@ -563,9 +556,8 @@ def _norm_iter(self, tps: Iterable[Any]) -> VarTuple[BaseNormType]:
     MUST_SUBSCRIBED_ORIGINS = [
         ClassVar, Final, Literal,
         Union, Optional, InitVar,
+        Annotated,
     ]
-    if HAS_ANNOTATED:
-        MUST_SUBSCRIBED_ORIGINS.append(typing.Annotated)
     if HAS_TYPE_GUARD:
         MUST_SUBSCRIBED_ORIGINS.append(typing.TypeGuard)
     if HAS_TYPED_DICT_REQUIRED:
@@ -584,9 +576,9 @@ def _norm_none(self, tp, origin, args):
         if origin is None or origin is NoneType:
             return _NormType(None, (), source=tp)
 
-    @_aspect_storage.add(condition=HAS_ANNOTATED)
+    @_aspect_storage.add
     def _norm_annotated(self, tp, origin, args):
-        if origin == typing.Annotated:
+        if origin == Annotated:
             return _AnnotatedNormType(
                 (self.normalize(args[0]), *args[1:]),
                 source=tp,
@@ -663,8 +655,8 @@ def _norm_new_type(self, tp, origin, args):
 
     @_aspect_storage.add
     def _norm_tuple(self, tp, origin, args):
-        if origin == tuple:
-            if tp in (tuple, Tuple):  # not subscribed values
+        if origin is tuple:
+            if tp in (tuple, typing.Tuple):  # not subscribed values
                 return _NormType(
                     tuple,
                     (ANY_NT, ...),
@@ -691,7 +683,7 @@ def _unpack_tuple_elements(self, args: VarTuple[BaseNormType]) -> VarTuple[BaseN
         # it is necessary to unpack the variable-length tuple as well
         if len(args) == 1 and args[0].origin == typing.Unpack:
             inner_tp = args[0].args[0]
-            if inner_tp.origin == tuple:
+            if inner_tp.origin is tuple:
                 return inner_tp.args
 
         return self._unpack_generic_elements(args)
@@ -706,7 +698,7 @@ def _unpack_generic_elements(self, args: VarTuple[Any]) -> VarTuple[BaseNormType
         return tuple(result)
 
     def _is_fixed_size_tuple(self, tp: BaseNormType) -> bool:
-        return tp.origin == tuple and (not tp.args or tp.args[-1] is not Ellipsis)
+        return tp.origin is tuple and (not tp.args or tp.args[-1] is not Ellipsis)
 
     @_aspect_storage.add
     def _norm_callable(self, tp, origin, args):
@@ -749,7 +741,7 @@ def _norm_literal(self, tp, origin, args):
             return _LiteralNormType(args, source=tp)
 
     def _unfold_union_args(self, norm_args: Iterable[N]) -> Iterable[N]:
-        result: List[N] = []
+        result: list[N] = []
         for norm in norm_args:
             if norm.origin == Union:
                 result.extend(norm.args)
@@ -758,7 +750,7 @@ def _unfold_union_args(self, norm_args: Iterable[N]) -> Iterable[N]:
         return result
 
     def _dedup_union_args(self, args: Iterable[BaseNormType]) -> Iterable[BaseNormType]:
-        args_to_sources: DefaultDict[BaseNormType, List[Any]] = defaultdict(list)
+        args_to_sources: defaultdict[BaseNormType, list[Any]] = defaultdict(list)
 
         for arg in args:
             args_to_sources[arg].append(arg.source)
@@ -772,7 +764,7 @@ def _dedup_union_args(self, args: Iterable[BaseNormType]) -> Iterable[BaseNormTy
 
     def _merge_literals(self, args: Iterable[N]) -> Sequence[N]:
         result = []
-        lit_args: List[N] = []
+        lit_args: list[N] = []
         for norm in args:
             if norm.origin == Literal:
                 lit_args.extend(norm.args)
@@ -783,7 +775,7 @@ def _merge_literals(self, args: Iterable[N]) -> Sequence[N]:
             result.append(_create_norm_literal(lit_args))
         return result
 
-    _UNION_ORIGINS: List[Any] = [Union]
+    _UNION_ORIGINS: list[Any] = [Union]
     if HAS_TYPE_UNION_OP:
         _UNION_ORIGINS.append(types.UnionType)
 
@@ -808,13 +800,13 @@ def _norm_type(self, tp, origin, args):
             if norm.origin == Union:
                 return _UnionNormType(
                     tuple(
-                        _NormType(type, (arg,), source=Type[arg.source])
+                        _NormType(type, (arg,), source=type[arg.source])
                         for arg in norm.args
                     ),
                     source=tp,
                 )
 
-    ALLOWED_ZERO_PARAMS_ORIGINS = {Any, NoReturn}
+    ALLOWED_ZERO_PARAMS_ORIGINS: set[Any] = {Any, NoReturn}
     if HAS_TYPE_ALIAS:
         ALLOWED_ZERO_PARAMS_ORIGINS.add(typing.TypeAlias)
     if HAS_PY_310:
diff --git a/src/adaptix/_internal/type_tools/type_evaler.py b/src/adaptix/_internal/type_tools/type_evaler.py
new file mode 100644
index 00000000..e3236e48
--- /dev/null
+++ b/src/adaptix/_internal/type_tools/type_evaler.py
@@ -0,0 +1,65 @@
+import ast
+import inspect
+from collections.abc import Callable, Sequence
+from types import ModuleType
+
+
+def make_fragments_collector(*, typing_modules: Sequence[str]) -> Callable[[ast.Module], list[ast.stmt]]:
+    def check_condition(expr: ast.expr) -> bool:
+        # searches for `TYPE_CHECKING`
+        if (
+            isinstance(expr, ast.Name)
+            and isinstance(expr.ctx, ast.Load)
+            and expr.id == "TYPE_CHECKING"
+        ):
+            return True
+
+        # searches for `typing.TYPE_CHECKING`
+        if (  # noqa: SIM103
+            isinstance(expr, ast.Attribute)
+            and expr.attr == "TYPE_CHECKING"
+            and isinstance(expr.ctx, ast.Load)
+            and isinstance(expr.value, ast.Name)
+            and expr.value.id in typing_modules
+            and isinstance(expr.value.ctx, ast.Load)
+        ):
+            return True
+        return False
+
+    def collect_type_checking_only_fragments(module: ast.Module) -> list[ast.stmt]:
+        fragments = []
+        for stmt in module.body:
+            if isinstance(stmt, ast.If) and not stmt.orelse and check_condition(stmt.test):
+                fragments.extend(stmt.body)
+
+        return fragments
+
+    return collect_type_checking_only_fragments
+
+
+default_collector = make_fragments_collector(typing_modules=["typing"])
+
+
+def exec_type_checking(
+    module: ModuleType,
+    *,
+    collector: Callable[[ast.Module], list[ast.stmt]] = default_collector,
+) -> None:
+    """This function scans module source code,
+    collects fragments under ``if TYPE_CHECKING`` and ``if typing.TYPE_CHECKING``
+    and executes them in the context of module.
+    After these, all imports and type definitions became available at runtime for analysis.
+
+    By default, it ignores ``if`` with ``else`` branch.
+
+    :param module: A module for processing
+    :param collector: A function collecting code fragments to execute
+    """
+    source = inspect.getsource(module)
+    fragments = collector(ast.parse(source))
+    code = compile(ast.Module(fragments, type_ignores=[]), f"<exec_type_checking of {module}>", "exec")
+    namespace = module.__dict__.copy()
+    exec(code, namespace)  # noqa: S102
+    for k, v in namespace.items():
+        if not hasattr(module, k):
+            setattr(module, k, v)
diff --git a/src/adaptix/_internal/utils.py b/src/adaptix/_internal/utils.py
index 2d246b45..94d027a4 100644
--- a/src/adaptix/_internal/utils.py
+++ b/src/adaptix/_internal/utils.py
@@ -2,23 +2,10 @@
 import sys
 import warnings
 from abc import ABC, abstractmethod
+from collections.abc import Collection, Generator, Iterable, Iterator, Mapping
 from contextlib import contextmanager
 from copy import copy
-from typing import (
-    Any,
-    Callable,
-    Collection,
-    Generator,
-    Iterable,
-    Iterator,
-    List,
-    Protocol,
-    Tuple,
-    TypeVar,
-    Union,
-    final,
-    overload,
-)
+from typing import Any, Callable, Generic, Protocol, TypeVar, Union, final, overload
 
 from .feature_requirement import HAS_NATIVE_EXC_GROUP, HAS_PY_310, HAS_PY_311
 
@@ -28,7 +15,7 @@
 class Cloneable(ABC):
     @abstractmethod
     def _calculate_derived(self) -> None:
-        ...
+        return
 
     @contextmanager
     @final
@@ -102,7 +89,7 @@ def __call__(cls):
 if HAS_PY_310:
     pairs = itertools.pairwise
 else:
-    def pairs(iterable: Iterable[T]) -> Iterable[Tuple[T, T]]:  # type: ignore[no-redef]
+    def pairs(iterable: Iterable[T]) -> Iterable[tuple[T, T]]:  # type: ignore[no-redef]
         it = iter(iterable)
         try:
             prev = next(it)
@@ -152,10 +139,10 @@ def __reversed__(self) -> Iterator[T]:
 
 def get_prefix_groups(
     values: Collection[ComparableSeqT],
-) -> Collection[Tuple[ComparableSeqT, Iterable[ComparableSeqT]]]:
-    groups: List[Tuple[ComparableSeqT, List[ComparableSeqT]]] = []
+) -> Collection[tuple[ComparableSeqT, Iterable[ComparableSeqT]]]:
+    groups: list[tuple[ComparableSeqT, list[ComparableSeqT]]] = []
     sorted_values = iter(sorted(values))
-    current_group: List[ComparableSeqT] = []
+    current_group: list[ComparableSeqT] = []
     try:
         prefix = next(sorted_values)
     except StopIteration:
@@ -232,3 +219,62 @@ def deprecated_alias_getter(name):
         return getattr(sys.modules[module_name], new_name)
 
     return deprecated_alias_getter
+
+
+MappingT = TypeVar("MappingT", bound=Mapping)
+
+
+class OrderedMappingHashWrapper(Generic[MappingT]):
+    __slots__ = ("mapping", "_hash")
+
+    def __init__(self, mapping: MappingT):
+        self.mapping = mapping
+        self._hash = hash(tuple(self.mapping.items()))
+
+    def __hash__(self):
+        return self._hash
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedMappingHashWrapper):
+            return self.mapping == other.mapping
+        return NotImplemented
+
+    def __repr__(self):
+        return f"OrderedMappingHashWrapper({self.mapping})"
+
+
+class MappingHashWrapper(Generic[MappingT]):
+    __slots__ = ("mapping", "_hash")
+
+    def __init__(self, mapping: MappingT):
+        self.mapping = mapping
+        self._hash = hash(frozenset(self.mapping.items()))
+
+    def __hash__(self):
+        return self._hash
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedMappingHashWrapper):
+            return self.mapping == other.mapping
+        return NotImplemented
+
+    def __repr__(self):
+        return f"MappingHashWrapper({self.mapping})"
+
+
+class AlwaysEqualHashWrapper(Generic[T]):
+    __slots__ = ("value", )
+
+    def __init__(self, value: T):
+        self.value = value
+
+    def __hash__(self):
+        return 0
+
+    def __eq__(self, other):
+        if isinstance(other, AlwaysEqualHashWrapper):
+            return True
+        return NotImplemented
+
+    def __repr__(self):
+        return f"AlwaysEqualHashWrapper({self.value})"
diff --git a/src/adaptix/retort.py b/src/adaptix/retort.py
index 84d7819d..d2cba313 100644
--- a/src/adaptix/retort.py
+++ b/src/adaptix/retort.py
@@ -1,5 +1,6 @@
 from adaptix._internal.retort.base_retort import BaseRetort
-from adaptix._internal.retort.operating_retort import OperatingRetort, ProviderNotFoundError
+from adaptix._internal.retort.operating_retort import OperatingRetort
+from adaptix._internal.retort.searching_retort import ProviderNotFoundError
 from adaptix._internal.utils import create_deprecated_alias_getter
 
 __all__ = (
diff --git a/src/adaptix/type_tools/__init__.py b/src/adaptix/type_tools/__init__.py
new file mode 100644
index 00000000..32af5b36
--- /dev/null
+++ b/src/adaptix/type_tools/__init__.py
@@ -0,0 +1,6 @@
+from adaptix._internal.type_tools import exec_type_checking, make_fragments_collector
+
+__all__ = (
+    "exec_type_checking",
+    "make_fragments_collector",
+)
diff --git a/tox.ini b/tox.ini
index ec6dd32d..ee5b8728 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,17 +4,15 @@ lint_mypy   = src/ scripts/ examples/ benchmarks/benchmarks/pybench/ docs/exampl
 lint_all    = src/ scripts/ examples/ benchmarks/benchmarks/pybench/ docs/examples/ docs/custom_ext/ tests/
 
 [tox]
-env_list = {py38, py39, py310, py311, py312, pypy38, pypy39, pypy310}-extra_{none, old, new},
+env_list = {py39, py310, py311, py312, pypy39, pypy310}-extra_{none, old, new},
            lint,
-           {py38, py39, py310, py311, py312, pypy38, pypy39, pypy310}-bench
+           {py39, py310, py311, py312, pypy39, pypy310}-bench
 
 [testenv]
 deps =
-  extra_none:                                                   -r requirements/test_extra_none.txt
-  extra_old:                                                    -r requirements/test_extra_old.txt
-
-  {py38, py39, py310, py311, py312, pypy39, pypy310}-extra_new: -r requirements/test_extra_new.txt
-  pypy38-extra_new:                                             -r requirements/test_extra_new_pypy38.txt
+  extra_none:  -r requirements/test_extra_none.txt
+  extra_old:   -r requirements/test_extra_old.txt
+  extra_new:   -r requirements/test_extra_new.txt
 
 use_develop = true
 
@@ -22,10 +20,9 @@ commands =
   pytest {posargs}
 
 
-[testenv:{py38, py39, py310, py311, py312, pypy38, pypy39, pypy310}-bench]
+[testenv:{py39, py310, py311, py312, pypy39, pypy310}-bench]
 deps =
-  {py38, py39, py310, py311, py312, pypy39, pypy310}: -r requirements/bench.txt
-  pypy38:                                             -r requirements/bench_pypy38.txt
+  -r requirements/bench.txt
 
 use_develop = true
 
@@ -38,8 +35,8 @@ deps = -r requirements/lint.txt
 base_python = python3.11
 
 install_commands =
-  python -m pip install -r requirements/pre.txt
-  python -m pip install {opts} {packages}
+  python -m uv pip install -r requirements/pre.txt
+  python -m uv pip install {opts} {packages}
 
 ignore_errors = true
 allowlist_externals = ruff


Test patch:
diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml
index 4d0e414d..c6598bb5 100644
--- a/.github/workflows/lint_and_test.yml
+++ b/.github/workflows/lint_and_test.yml
@@ -36,6 +36,8 @@ jobs:
           just-version: 1.14.0
 
       - name: Setup environment to run tox
+        env:
+          UV_SYSTEM_PYTHON: 1
         run:
           just setup-runner
 
@@ -52,15 +54,14 @@ jobs:
       fail-fast: true
       matrix:
         python_version:
-          - { setup: '3.8',      tox: 'py38',    cov: true  }
           - { setup: '3.9',      tox: 'py39',    cov: true  }
           - { setup: '3.10',     tox: 'py310',   cov: true  }
           - { setup: '3.11',     tox: 'py311',   cov: true  }
-          - { setup: '3.12',     tox: 'py312',   cov: true  }
-          - { setup: 'pypy3.8',  tox: 'pypy38',  cov: false }
+          - { setup: '3.12.3',   tox: 'py312',   cov: true  }  # (1)
           - { setup: 'pypy3.9',  tox: 'pypy39',  cov: false }
           - { setup: 'pypy3.10', tox: 'pypy310', cov: false }
 
+        # (1): 3.12.4 contains backwards-incompatible changes of ForwardRef._evaluate()
         os: ['ubuntu-latest']
 
     steps:
@@ -77,6 +78,8 @@ jobs:
           just-version: 1.14.0
 
       - name: Setup environment to run tox
+        env:
+          UV_SYSTEM_PYTHON: 1
         run:
           just setup-runner
 
diff --git a/.github/workflows/test_all_oses.yml b/.github/workflows/test_all_oses.yml
index 38f27145..ecf3ee27 100644
--- a/.github/workflows/test_all_oses.yml
+++ b/.github/workflows/test_all_oses.yml
@@ -10,6 +10,9 @@ concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
   cancel-in-progress: true
 
+env:
+  UV_SYSTEM_PYTHON: 1
+
 jobs:
   testing:
     name: Testing (${{ matrix.python_version.tox }}, ${{ matrix.os }})
@@ -19,7 +22,6 @@ jobs:
       fail-fast: false
       matrix:
         python_version:
-          - { setup: '3.8',      tox: 'py38'    }
           - { setup: '3.9',      tox: 'py39'    }
           - { setup: '3.10',     tox: 'py310'   }
           - { setup: '3.11',     tox: 'py311'   }
@@ -44,6 +46,8 @@ jobs:
           just-version: 1.14.0
 
       - name: Setup environment to run tox
+        env:
+          UV_SYSTEM_PYTHON: 1
         run:
           just setup-runner
 
diff --git a/examples/api_division/test_example.py b/examples/api_division/test_example.py
index 4bccff4b..63112dc3 100644
--- a/examples/api_division/test_example.py
+++ b/examples/api_division/test_example.py
@@ -105,7 +105,7 @@ def test_outer_loading_bad_phone():
             [
                 with_trail(
                     UnionLoadError(
-                        f"while loading {Optional[List[NotifyTarget]]}",
+                        f"while loading {Optional[list[NotifyTarget]]}",
                         [
                             TypeLoadError(None, [{"type": "phone", "value": "+1-541-754-3010"}]),
                             AggregateLoadError(
diff --git a/requirements/raw/test_extra_new.txt b/requirements/raw/test_extra_new.txt
index 58eea503..9122353d 100644
--- a/requirements/raw/test_extra_new.txt
+++ b/requirements/raw/test_extra_new.txt
@@ -1,4 +1,4 @@
 -r test_extra_none.txt
-attrs==23.2.0
-sqlalchemy==2.0.30
-pydantic==2.7.1
+attrs==24.2.0
+sqlalchemy==2.0.32
+pydantic==2.8.2
diff --git a/requirements/raw/test_extra_new_pypy38.txt b/requirements/raw/test_extra_new_pypy38.txt
deleted file mode 100644
index 479bfb24..00000000
--- a/requirements/raw/test_extra_new_pypy38.txt
+++ /dev/null
@@ -1,4 +0,0 @@
--r test_extra_none.txt
-attrs==23.2.0
-sqlalchemy==2.0.30
-pydantic==2.5.3   # last version with pypy38 support
diff --git a/requirements/raw/test_extra_old.txt b/requirements/raw/test_extra_old.txt
index c49d6391..624fd216 100644
--- a/requirements/raw/test_extra_old.txt
+++ b/requirements/raw/test_extra_old.txt
@@ -1,10 +1,4 @@
 -r test_extra_none.txt
 attrs==21.3.0
 sqlalchemy==2.0.0
-pydantic==2.0.0
-
-# pydantic-core has dependency:
-# `typing-extensions >=4.6.0,<4.7.0; platform_python_implementation == "PyPy"`
-# Final requirements file generated on cpython ignoring this contraint.
-# This is leads to ResolutionImpossible error on PyPy run
-typing-extensions>=4.6.0, <4.7.0
+pydantic==2.1.0
diff --git a/requirements/test_extra_new.txt b/requirements/test_extra_new.txt
index 933dd4ee..b7ae4438 100644
--- a/requirements/test_extra_new.txt
+++ b/requirements/test_extra_new.txt
@@ -1,40 +1,36 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/test_extra_new.txt --strip-extras requirements/raw/test_extra_new.txt
-#
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/test_extra_new.txt -o requirements/test_extra_new.txt --allow-unsafe --strip-extras --no-strip-markers
 -e ./tests/tests_helpers
     # via -r requirements/raw/test_extra_none.txt
-annotated-types==0.6.0
+annotated-types==0.7.0
     # via pydantic
-attrs==23.2.0
+attrs==24.2.0
     # via -r requirements/raw/test_extra_new.txt
 coverage==7.4.4
     # via -r requirements/raw/test_extra_none.txt
 dirty-equals==0.7.1.post0
     # via -r requirements/raw/test_extra_none.txt
-greenlet==3.0.3
+greenlet==3.0.3 ; (python_full_version < '3.13' and platform_machine == 'AMD64') or (python_full_version < '3.13' and platform_machine == 'WIN32') or (python_full_version < '3.13' and platform_machine == 'aarch64') or (python_full_version < '3.13' and platform_machine == 'amd64') or (python_full_version < '3.13' and platform_machine == 'ppc64le') or (python_full_version < '3.13' and platform_machine == 'win32') or (python_full_version < '3.13' and platform_machine == 'x86_64')
     # via sqlalchemy
 iniconfig==2.0.0
     # via pytest
-packaging==24.0
+packaging==24.1
     # via pytest
 phonenumberslite==8.13.26
     # via -r requirements/raw/test_extra_none.txt
 pluggy==1.5.0
     # via pytest
-pydantic==2.7.1
+pydantic==2.8.2
     # via -r requirements/raw/test_extra_new.txt
-pydantic-core==2.18.2
+pydantic-core==2.20.1
     # via pydantic
 pytest==7.4.2
     # via -r requirements/raw/test_extra_none.txt
 pytz==2024.1
     # via dirty-equals
-sqlalchemy==2.0.30
+sqlalchemy==2.0.32
     # via -r requirements/raw/test_extra_new.txt
-typing-extensions==4.11.0
+typing-extensions==4.12.2
     # via
     #   pydantic
     #   pydantic-core
diff --git a/requirements/test_extra_new_pypy38.txt b/requirements/test_extra_new_pypy38.txt
deleted file mode 100644
index 8260bb8a..00000000
--- a/requirements/test_extra_new_pypy38.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/test_extra_new_pypy38.txt --strip-extras requirements/raw/test_extra_new_pypy38.txt
-#
--e ./tests/tests_helpers
-    # via -r requirements/raw/test_extra_none.txt
-annotated-types==0.6.0
-    # via pydantic
-attrs==23.2.0
-    # via -r requirements/raw/test_extra_new_pypy38.txt
-coverage==7.4.4
-    # via -r requirements/raw/test_extra_none.txt
-dirty-equals==0.7.1.post0
-    # via -r requirements/raw/test_extra_none.txt
-greenlet==3.0.3
-    # via sqlalchemy
-iniconfig==2.0.0
-    # via pytest
-packaging==24.0
-    # via pytest
-phonenumberslite==8.13.26
-    # via -r requirements/raw/test_extra_none.txt
-pluggy==1.5.0
-    # via pytest
-pydantic==2.5.3
-    # via -r requirements/raw/test_extra_new_pypy38.txt
-pydantic-core==2.14.6
-    # via pydantic
-pytest==7.4.2
-    # via -r requirements/raw/test_extra_none.txt
-pytz==2024.1
-    # via dirty-equals
-sqlalchemy==2.0.30
-    # via -r requirements/raw/test_extra_new_pypy38.txt
-typing-extensions==4.11.0
-    # via
-    #   pydantic
-    #   pydantic-core
-    #   sqlalchemy
diff --git a/requirements/test_extra_none.txt b/requirements/test_extra_none.txt
index 077a0c7f..522d45d3 100644
--- a/requirements/test_extra_none.txt
+++ b/requirements/test_extra_none.txt
@@ -1,9 +1,5 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/test_extra_none.txt --strip-extras requirements/raw/test_extra_none.txt
-#
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/test_extra_none.txt -o requirements/test_extra_none.txt --allow-unsafe --strip-extras --no-strip-markers
 -e ./tests/tests_helpers
     # via -r requirements/raw/test_extra_none.txt
 coverage==7.4.4
@@ -12,7 +8,7 @@ dirty-equals==0.7.1.post0
     # via -r requirements/raw/test_extra_none.txt
 iniconfig==2.0.0
     # via pytest
-packaging==24.0
+packaging==24.1
     # via pytest
 phonenumberslite==8.13.26
     # via -r requirements/raw/test_extra_none.txt
diff --git a/requirements/test_extra_old.txt b/requirements/test_extra_old.txt
index d1bfe81d..1472d867 100644
--- a/requirements/test_extra_old.txt
+++ b/requirements/test_extra_old.txt
@@ -1,12 +1,8 @@
-#
-# This file is autogenerated by pip-compile with Python 3.12
-# by the following command:
-#
-#    pip-compile --allow-unsafe --output-file=requirements/test_extra_old.txt --strip-extras requirements/raw/test_extra_old.txt
-#
+# This file was autogenerated by uv via the following command:
+#    uv pip compile requirements/raw/test_extra_old.txt -o requirements/test_extra_old.txt --allow-unsafe --strip-extras --no-strip-markers
 -e ./tests/tests_helpers
     # via -r requirements/raw/test_extra_none.txt
-annotated-types==0.6.0
+annotated-types==0.7.0
     # via pydantic
 attrs==21.3.0
     # via -r requirements/raw/test_extra_old.txt
@@ -14,19 +10,19 @@ coverage==7.4.4
     # via -r requirements/raw/test_extra_none.txt
 dirty-equals==0.7.1.post0
     # via -r requirements/raw/test_extra_none.txt
-greenlet==3.0.3
+greenlet==3.0.3 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'
     # via sqlalchemy
 iniconfig==2.0.0
     # via pytest
-packaging==24.0
+packaging==24.1
     # via pytest
 phonenumberslite==8.13.26
     # via -r requirements/raw/test_extra_none.txt
 pluggy==1.5.0
     # via pytest
-pydantic==2.0
+pydantic==2.1.0
     # via -r requirements/raw/test_extra_old.txt
-pydantic-core==2.0.1
+pydantic-core==2.4.0
     # via pydantic
 pytest==7.4.2
     # via -r requirements/raw/test_extra_none.txt
@@ -34,9 +30,8 @@ pytz==2024.1
     # via dirty-equals
 sqlalchemy==2.0.0
     # via -r requirements/raw/test_extra_old.txt
-typing-extensions==4.6.3
+typing-extensions==4.12.2
     # via
-    #   -r requirements/raw/test_extra_old.txt
     #   pydantic
     #   pydantic-core
     #   sqlalchemy
diff --git a/tests/conftest.py b/tests/conftest.py
index 66b70517..0e21a93d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -21,17 +21,17 @@ def trail_select(debug_trail):
 
 
 @pytest.fixture()
-def model_spec() -> ModelSpecSchema:  # noqa: PT004
+def model_spec() -> ModelSpecSchema:
     ...
 
 
 @pytest.fixture()
-def src_model_spec() -> ModelSpecSchema:  # noqa: PT004
+def src_model_spec() -> ModelSpecSchema:
     ...
 
 
 @pytest.fixture()
-def dst_model_spec() -> ModelSpecSchema:  # noqa: PT004
+def dst_model_spec() -> ModelSpecSchema:
     ...
 
 
diff --git a/tests/integration/conversion/test_basics.py b/tests/integration/conversion/test_basics.py
index 1f455104..ccede93a 100644
--- a/tests/integration/conversion/test_basics.py
+++ b/tests/integration/conversion/test_basics.py
@@ -1,8 +1,7 @@
-from typing import Any, Generic, TypeVar
+from typing import Annotated, Any, Generic, TypeVar
 
-from tests_helpers import ModelSpec, exclude_model_spec, only_generic_models, requires
+from tests_helpers import ModelSpec, exclude_model_spec, only_generic_models
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix.conversion import impl_converter
 
 
@@ -192,10 +191,7 @@ def convert(a: SourceModel) -> DestModel:
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated_ignoring(src_model_spec, dst_model_spec):
-    from typing import Annotated
-
     @src_model_spec.decorator
     class SourceModel(*src_model_spec.bases):
         field1: Any
diff --git a/tests/integration/conversion/test_coercer.py b/tests/integration/conversion/test_coercer.py
index abeb9314..a21be391 100644
--- a/tests/integration/conversion/test_coercer.py
+++ b/tests/integration/conversion/test_coercer.py
@@ -1,14 +1,11 @@
-import typing
 from datetime import datetime, timezone
-from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Set, Tuple, Union
+from typing import Annotated, Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Set, Tuple, Union
 
 import pytest
-from tests_helpers import cond_list
 from tests_helpers.model_spec import ModelSpec
 
 from adaptix import P
 from adaptix._internal.conversion.facade.provider import from_param, link
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix.conversion import coercer, impl_converter
 
 
@@ -154,15 +151,10 @@ def convert(a: SourceModel) -> DestModel:
         pytest.param(Optional[str], Optional[str], None, None),
         pytest.param(Optional[bool], Optional[int], True, True),
         pytest.param(Optional[str], Optional[int], "123", 123),
-        *cond_list(
-            HAS_ANNOTATED,
-            lambda: [
-                pytest.param(Optional[typing.Annotated[int, "meta"]], Optional[int], 123, 123),
-                pytest.param(Optional[int], Optional[typing.Annotated[int, "meta"]], 123, 123),
-                pytest.param(typing.Annotated[Optional[int], "meta"], Optional[int], 123, 123),
-                pytest.param(Optional[int], typing.Annotated[Optional[int], "meta"], 123, 123),
-            ],
-        ),
+        pytest.param(Optional[Annotated[int, "meta"]], Optional[int], 123, 123),
+        pytest.param(Optional[int], Optional[Annotated[int, "meta"]], 123, 123),
+        pytest.param(Annotated[Optional[int], "meta"], Optional[int], 123, 123),
+        pytest.param(Optional[int], Annotated[Optional[int], "meta"], 123, 123),
     ],
 )
 def test_optional(model_spec, src_tp, dst_tp, src_value, dst_value):
diff --git a/tests/integration/conversion/test_not_a_model.py b/tests/integration/conversion/test_not_a_model.py
new file mode 100644
index 00000000..0bcad1ab
--- /dev/null
+++ b/tests/integration/conversion/test_not_a_model.py
@@ -0,0 +1,135 @@
+from dataclasses import dataclass
+
+from tests_helpers import raises_exc, with_cause, with_notes
+
+from adaptix import AggregateCannotProvide, CannotProvide, ProviderNotFoundError
+from adaptix.conversion import get_converter
+
+
+def test_source_is_not_a_model():
+    class Book:
+        title: str
+        price: int
+        author: int
+
+    @dataclass
+    class BookDTO:
+        title: str
+        price: int
+        author: int
+
+    raises_exc(
+        with_cause(
+            with_notes(
+                ProviderNotFoundError(
+                    f"Cannot produce converter for"
+                    f" <Signature (src: {Book.__module__}.{Book.__qualname__}, /)"
+                    f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
+                ),
+                "Note: The attached exception above contains verbose description of the problem",
+            ),
+            AggregateCannotProvide(
+                "Cannot create top-level coercer",
+                [
+                    with_notes(
+                        CannotProvide(
+                            "Cannot find coercer",
+                            is_terminal=False,
+                            is_demonstrative=True,
+                        ),
+                        f"Linking: `{Book.__qualname__} => {BookDTO.__qualname__}`",
+                        "Hint: Class `Book` is not recognized as model. Did your forget `@dataclass` decorator?"
+                        " Check documentation what model kinds are supported",
+                    ),
+                ],
+                is_terminal=True,
+                is_demonstrative=True,
+            ),
+        ),
+        lambda: get_converter(Book, BookDTO),
+    )
+
+
+def test_destination_is_not_a_model():
+    @dataclass
+    class Book:
+        title: str
+        price: int
+        author: int
+
+    class BookDTO:
+        title: str
+        price: int
+        author: int
+
+    raises_exc(
+        with_cause(
+            with_notes(
+                ProviderNotFoundError(
+                    f"Cannot produce converter for"
+                    f" <Signature (src: {Book.__module__}.{Book.__qualname__}, /)"
+                    f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
+                ),
+                "Note: The attached exception above contains verbose description of the problem",
+            ),
+            AggregateCannotProvide(
+                "Cannot create top-level coercer",
+                [
+                    with_notes(
+                        CannotProvide(
+                            "Cannot find coercer",
+                            is_terminal=False,
+                            is_demonstrative=True,
+                        ),
+                        f"Linking: `{Book.__qualname__} => {BookDTO.__qualname__}`",
+                        "Hint: Class `BookDTO` is not recognized as model. Did your forget `@dataclass` decorator?"
+                        " Check documentation what model kinds are supported",
+                    ),
+                ],
+                is_terminal=True,
+                is_demonstrative=True,
+            ),
+        ),
+        lambda: get_converter(Book, BookDTO),
+    )
+
+
+def test_both_are_not_a_model():
+    class Book:
+        title: str
+        price: int
+        author: int
+
+    class BookDTO:
+        title: str
+        price: int
+        author: int
+
+    raises_exc(
+        with_cause(
+            with_notes(
+                ProviderNotFoundError(
+                    f"Cannot produce converter for"
+                    f" <Signature (src: {Book.__module__}.{Book.__qualname__}, /)"
+                    f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
+                ),
+                "Note: The attached exception above contains verbose description of the problem",
+            ),
+            AggregateCannotProvide(
+                "Cannot create top-level coercer",
+                [
+                    with_notes(
+                        CannotProvide(
+                            "Cannot find coercer",
+                            is_terminal=False,
+                            is_demonstrative=True,
+                        ),
+                        f"Linking: `{Book.__qualname__} => {BookDTO.__qualname__}`",
+                    ),
+                ],
+                is_terminal=True,
+                is_demonstrative=True,
+            ),
+        ),
+        lambda: get_converter(Book, BookDTO),
+    )
diff --git a/tests/integration/morphing/test_sqlalchemy.py b/tests/integration/morphing/test_sqlalchemy.py
index 0d97a4b5..0fc825b6 100644
--- a/tests/integration/morphing/test_sqlalchemy.py
+++ b/tests/integration/morphing/test_sqlalchemy.py
@@ -3,10 +3,9 @@
 import pytest
 from sqlalchemy import ForeignKey
 from sqlalchemy.orm import Mapped, mapped_column, registry, relationship
-from tests_helpers import cond_list, sqlalchemy_equals
+from tests_helpers import sqlalchemy_equals
 
 from adaptix import Retort
-from adaptix._internal.feature_requirement import HAS_STD_CLASSES_GENERICS
 
 
 def test_simple(accum):
@@ -95,10 +94,7 @@ class Declarative2:
     "list_tp",
     [
         List,
-        *cond_list(
-            HAS_STD_CLASSES_GENERICS,
-            [list],
-        ),
+        list,
     ],
 )
 def test_o2m_relationship(accum, list_tp):
diff --git a/tests/tests_helpers/tests_helpers/misc.py b/tests/tests_helpers/tests_helpers/misc.py
index 96d9a9b0..a72cb75a 100644
--- a/tests/tests_helpers/tests_helpers/misc.py
+++ b/tests/tests_helpers/tests_helpers/misc.py
@@ -1,21 +1,25 @@
 import dataclasses
+import importlib.util
 import inspect
 import re
 import runpy
 import sys
+from collections.abc import Generator, Reversible, Sequence
 from contextlib import contextmanager
 from dataclasses import dataclass, is_dataclass
 from pathlib import Path
 from types import ModuleType, SimpleNamespace
-from typing import Any, Callable, Dict, Generator, List, Optional, Reversible, Type, TypeVar, Union
+from typing import Any, Callable, Optional, TypeVar, Union
 from uuid import uuid4
 
 import pytest
 
-from adaptix import CannotProvide, DebugTrail, Mediator, Provider, ProviderNotFoundError, Request
+from adaptix import CannotProvide, DebugTrail, Provider, ProviderNotFoundError, Request, Retort
 from adaptix._internal.compat import CompatExceptionGroup
 from adaptix._internal.feature_requirement import DistributionVersionRequirement, Requirement
 from adaptix._internal.morphing.model.basic_gen import CodeGenAccumulator
+from adaptix._internal.provider.essential import Mediator, RequestChecker, RequestHandler
+from adaptix._internal.retort.operating_retort import OperatingRetort
 from adaptix._internal.struct_trail import TrailElement, extend_trail, render_trail_as_note
 from adaptix._internal.type_tools import is_parametrized
 from adaptix._internal.utils import add_note
@@ -39,7 +43,7 @@ def wrapper(func):
 E = TypeVar("E", bound=Exception)
 
 
-def _repr_value(obj: Any) -> Dict[str, Any]:
+def _repr_value(obj: Any) -> dict[str, Any]:
     if not isinstance(obj, Exception):
         return obj
 
@@ -72,7 +76,7 @@ def _repr_value(obj: Any) -> Dict[str, Any]:
 
 
 def raises_exc(
-    exc: Union[Type[E], E],
+    exc: Union[type[E], E],
     func: Callable[[], Any],
     *,
     match: Optional[str] = None,
@@ -129,8 +133,8 @@ def source_namespace(self):
 class PlaceholderProvider(Provider):
     value: int
 
-    def apply_provider(self, mediator: Mediator, request: Request[T]) -> T:
-        raise CannotProvide
+    def get_request_handlers(self) -> Sequence[tuple[type[Request], RequestChecker, RequestHandler]]:
+        return []
 
 
 def full_match(string_to_match: str) -> str:
@@ -171,7 +175,7 @@ def __call__(self, *, disable: T1, first: T2, all: T3) -> Union[T1, T2, T3]:  #
 def load_namespace(
     file_name: str,
     ns_id: Optional[str] = None,
-    vars: Optional[Dict[str, Any]] = None,  # noqa: A002
+    vars: Optional[dict[str, Any]] = None,  # noqa: A002
     run_name: Optional[str] = None,
     stack_offset: int = 1,
 ) -> SimpleNamespace:
@@ -186,11 +190,20 @@ def load_namespace(
     return SimpleNamespace(**ns_dict)
 
 
+@contextmanager
+def temp_module(module: ModuleType):
+    sys.modules[module.__name__] = module
+    try:
+        yield
+    finally:
+        sys.modules.pop(module.__name__, None)
+
+
 @contextmanager
 def load_namespace_keeping_module(
     file_name: str,
     ns_id: Optional[str] = None,
-    vars: Optional[Dict[str, Any]] = None,  # noqa: A002
+    vars: Optional[dict[str, Any]] = None,  # noqa: A002
     run_name: Optional[str] = None,
 ) -> Generator[SimpleNamespace, None, None]:
     if run_name is None:
@@ -199,14 +212,22 @@ def load_namespace_keeping_module(
     module = ModuleType(run_name)
     for attr, value in ns.__dict__.items():
         setattr(module, attr, value)
-    sys.modules[run_name] = module
-    try:
+
+    with temp_module(module):
         yield ns
-    finally:
-        sys.modules.pop(run_name, None)
 
 
-def with_notes(exc: E, *notes: Union[str, List[str]]) -> E:
+def import_local_module(file_path: Path, name: Optional[str] = None) -> ModuleType:
+    if name is None:
+        name = file_path.stem
+
+    spec = importlib.util.spec_from_file_location(name, file_path)
+    module = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(module)
+    return module
+
+
+def with_notes(exc: E, *notes: Union[str, list[str]]) -> E:
     for note_or_list in notes:
         if isinstance(note_or_list, list):
             for note in note_or_list:
@@ -231,3 +252,14 @@ def _evaluate(self) -> bool:
     @property
     def fail_reason(self) -> str:
         return self._fail_reason
+
+
+class StubRequest(Request):
+    pass
+
+
+stub_retort = Retort()
+
+
+def create_mediator(retort: OperatingRetort = stub_retort) -> Mediator:
+    return retort._create_mediator(StubRequest())
diff --git a/tests/tests_helpers/tests_helpers/model_spec.py b/tests/tests_helpers/tests_helpers/model_spec.py
index 5caf493e..41e60c85 100644
--- a/tests/tests_helpers/tests_helpers/model_spec.py
+++ b/tests/tests_helpers/tests_helpers/model_spec.py
@@ -1,8 +1,9 @@
+from collections.abc import Mapping
 from dataclasses import dataclass
 from enum import Enum
 from operator import getitem
 from types import ModuleType
-from typing import Any, Callable, Mapping, NamedTuple, TypedDict, Union
+from typing import Any, Callable, NamedTuple, TypedDict, Union
 
 import pytest
 from _pytest.python import Metafunc
diff --git a/tests/unit/model_tools/introspection/test_attrs.py b/tests/unit/model_tools/introspection/test_attrs.py
index 10bdcd3b..1a1237aa 100644
--- a/tests/unit/model_tools/introspection/test_attrs.py
+++ b/tests/unit/model_tools/introspection/test_attrs.py
@@ -1,7 +1,6 @@
-import typing
 from dataclasses import dataclass
 from types import MappingProxyType
-from typing import Any, Tuple
+from typing import Annotated, Any, Tuple
 from unittest.mock import ANY
 
 import attr
@@ -9,7 +8,6 @@
 from attrs import Factory, define, field
 from tests_helpers import ATTRS_WITH_ALIAS, requires
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix._internal.model_tools.definitions import (
     DefaultFactory,
     DefaultFactoryWithSelf,
@@ -739,11 +737,10 @@ def test_none_attr_custom_init():
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated():
     @define
     class WithAnnotated:
-        a: typing.Annotated[int, "metadata"]
+        a: Annotated[int, "metadata"]
 
     assert (
         get_attrs_shape(WithAnnotated)
@@ -754,7 +751,7 @@ class WithAnnotated:
                 kwargs=None,
                 fields=(
                     InputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="a",
                         default=NoDefault(),
                         is_required=True,
@@ -774,7 +771,7 @@ class WithAnnotated:
             output=OutputShape(
                 fields=(
                     OutputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="a",
                         default=NoDefault(),
                         accessor=create_attr_accessor("a", is_required=True),
diff --git a/tests/unit/model_tools/introspection/test_class_init.py b/tests/unit/model_tools/introspection/test_class_init.py
index 8f0c851b..377a793b 100644
--- a/tests/unit/model_tools/introspection/test_class_init.py
+++ b/tests/unit/model_tools/introspection/test_class_init.py
@@ -1,12 +1,9 @@
-import typing
 from types import MappingProxyType
-from typing import Any
+from typing import Annotated, Any
 from unittest.mock import ANY
 
 import pytest
-from tests_helpers import requires
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix._internal.model_tools.definitions import (
     DefaultValue,
     InputField,
@@ -265,10 +262,9 @@ def __init__(self, a, b, *args):
         get_class_init_shape(HasVarArg)
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated():
     class WithAnnotated:
-        def __init__(self, a: typing.Annotated[int, "metadata"]):
+        def __init__(self, a: Annotated[int, "metadata"]):
             pass
 
     assert (
@@ -280,7 +276,7 @@ def __init__(self, a: typing.Annotated[int, "metadata"]):
                 kwargs=None,
                 fields=(
                     InputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="a",
                         default=NoDefault(),
                         is_required=True,
diff --git a/tests/unit/model_tools/introspection/test_dataclass.py b/tests/unit/model_tools/introspection/test_dataclass.py
index 31f81295..f8f480a3 100644
--- a/tests/unit/model_tools/introspection/test_dataclass.py
+++ b/tests/unit/model_tools/introspection/test_dataclass.py
@@ -1,13 +1,12 @@
-import typing
 from dataclasses import InitVar, dataclass, field
 from types import MappingProxyType
-from typing import ClassVar
+from typing import Annotated, ClassVar
 from unittest.mock import ANY
 
 import pytest
 from tests_helpers import requires
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED, HAS_PY_310
+from adaptix._internal.feature_requirement import HAS_PY_310
 from adaptix._internal.model_tools.definitions import (
     DefaultFactory,
     DefaultValue,
@@ -375,11 +374,10 @@ def test_forward_ref():
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated():
     @dataclass
     class WithAnnotated:
-        annotated_field: typing.Annotated[int, "metadata"]
+        annotated_field: Annotated[int, "metadata"]
 
     assert (
         get_dataclass_shape(WithAnnotated)
@@ -390,7 +388,7 @@ class WithAnnotated:
                 kwargs=None,
                 fields=(
                     InputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         is_required=True,
@@ -410,7 +408,7 @@ class WithAnnotated:
             output=OutputShape(
                 fields=(
                     OutputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         accessor=create_attr_accessor("annotated_field", is_required=True),
diff --git a/tests/unit/model_tools/introspection/test_namedtuple.py b/tests/unit/model_tools/introspection/test_namedtuple.py
index 6d410a4c..bd910796 100644
--- a/tests/unit/model_tools/introspection/test_namedtuple.py
+++ b/tests/unit/model_tools/introspection/test_namedtuple.py
@@ -1,13 +1,9 @@
 # ruff: noqa: PYI024, UP014, FBT003
-import typing
 from collections import namedtuple
 from types import MappingProxyType
-from typing import Any, NamedTuple
+from typing import Annotated, Any, NamedTuple
 from unittest.mock import ANY
 
-from tests_helpers import requires
-
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix._internal.model_tools.definitions import (
     DefaultValue,
     InputField,
@@ -713,10 +709,9 @@ class Child(Parent):
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated():
     class WithAnnotated(NamedTuple):
-        annotated_field: typing.Annotated[int, "metadata"]
+        annotated_field: Annotated[int, "metadata"]
 
     assert (
         get_named_tuple_shape(WithAnnotated)
@@ -727,7 +722,7 @@ class WithAnnotated(NamedTuple):
                 kwargs=None,
                 fields=(
                     InputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         is_required=True,
@@ -747,7 +742,7 @@ class WithAnnotated(NamedTuple):
             output=OutputShape(
                 fields=(
                     OutputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         accessor=create_key_accessor(0, access_error=None),
diff --git a/tests/unit/model_tools/introspection/test_pydantic.py b/tests/unit/model_tools/introspection/test_pydantic.py
index 09b0de22..b77265be 100644
--- a/tests/unit/model_tools/introspection/test_pydantic.py
+++ b/tests/unit/model_tools/introspection/test_pydantic.py
@@ -1,5 +1,5 @@
 from functools import cached_property
-from typing import Any
+from typing import Annotated, Any
 from unittest.mock import ANY
 
 import pytest
@@ -7,9 +7,8 @@
 from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, computed_field
 from pydantic.fields import AliasChoices, AliasPath, ModelPrivateAttr
 from pydantic_core import PydanticUndefined
-from tests_helpers import parametrize_bool, raises_exc, requires
+from tests_helpers import parametrize_bool, raises_exc
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix._internal.model_tools.definitions import (
     ClarifiedIntrospectionError,
     DefaultFactory,
@@ -635,10 +634,7 @@ def __init__(self, f1: int, **kwargs):
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated():
-    from typing import Annotated
-
     class MyModel(BaseModel):
         f1: Annotated[str, "meta"]
 
@@ -711,10 +707,7 @@ def f3(self) -> str:
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_field_constraints():
-    from typing import Annotated
-
     class MyModel(BaseModel):
         f1: int = Field(gt=1, ge=10)
 
diff --git a/tests/unit/model_tools/introspection/test_sqlalchemy.py b/tests/unit/model_tools/introspection/test_sqlalchemy.py
index 5b60b71a..465a3dba 100644
--- a/tests/unit/model_tools/introspection/test_sqlalchemy.py
+++ b/tests/unit/model_tools/introspection/test_sqlalchemy.py
@@ -1,11 +1,9 @@
-from typing import Optional
+from typing import Annotated, Optional
 from unittest.mock import ANY
 
 from sqlalchemy import Column, ForeignKey, Integer, String, Table
 from sqlalchemy.orm import Mapped, mapped_column, registry, relationship
-from tests_helpers import requires
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix._internal.model_tools.definitions import (
     DefaultFactory,
     DefaultValue,
@@ -513,10 +511,7 @@ class Imperative2:
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_declarative_annotated():
-    from typing import Annotated
-
     mapper_registry = registry()
 
     @mapper_registry.mapped
diff --git a/tests/unit/model_tools/introspection/test_typed_dict.py b/tests/unit/model_tools/introspection/test_typed_dict.py
index 547c9d61..e5feacd7 100644
--- a/tests/unit/model_tools/introspection/test_typed_dict.py
+++ b/tests/unit/model_tools/introspection/test_typed_dict.py
@@ -1,11 +1,11 @@
 # ruff: noqa: FBT001, FBT003
 import typing
 from types import MappingProxyType
-from typing import TypedDict
+from typing import Annotated, TypedDict
 
 from tests_helpers import requires
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED, HAS_PY_39, HAS_TYPED_DICT_REQUIRED
+from adaptix._internal.feature_requirement import HAS_TYPED_DICT_REQUIRED
 from adaptix._internal.model_tools.definitions import (
     InputField,
     InputShape,
@@ -216,10 +216,6 @@ class GrandChildNotTotal(ChildTotal, total=False):
     z: str
 
 
-def _negate_if_not_py39(value: bool) -> bool:
-    return value if HAS_PY_39 else not value
-
-
 def test_inheritance_first():
     assert (
         get_typed_dict_shape(ParentNotTotal)
@@ -277,7 +273,7 @@ def test_inheritance_second():
                         type=int,
                         id="x",
                         default=NoDefault(),
-                        is_required=_negate_if_not_py39(False),
+                        is_required=False,
                         metadata=MappingProxyType({}),
                         original=None,
                     ),
@@ -311,7 +307,7 @@ def test_inheritance_second():
                         id="x",
                         default=NoDefault(),
                         metadata=MappingProxyType({}),
-                        accessor=create_key_accessor("x", access_error=KeyError if HAS_PY_39 else None),
+                        accessor=create_key_accessor("x", access_error=KeyError),
                         original=None,
                     ),
                     OutputField(
@@ -350,7 +346,7 @@ def test_inheritance_third():
                         type=str,
                         id="y",
                         default=NoDefault(),
-                        is_required=_negate_if_not_py39(True),
+                        is_required=True,
                         metadata=MappingProxyType({}),
                         original=None,
                     ),
@@ -397,7 +393,7 @@ def test_inheritance_third():
                         id="y",
                         default=NoDefault(),
                         metadata=MappingProxyType({}),
-                        accessor=create_key_accessor("y", access_error=None if HAS_PY_39 else KeyError),
+                        accessor=create_key_accessor("y", access_error=None),
                         original=None,
                     ),
                     OutputField(
@@ -415,10 +411,9 @@ def test_inheritance_third():
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated():
     class WithAnnotatedTotal(TypedDict):
-        annotated_field: typing.Annotated[int, "metadata"]
+        annotated_field: Annotated[int, "metadata"]
 
     assert (
         get_typed_dict_shape(WithAnnotatedTotal)
@@ -429,7 +424,7 @@ class WithAnnotatedTotal(TypedDict):
                 kwargs=None,
                 fields=(
                     InputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         is_required=True,
@@ -449,7 +444,7 @@ class WithAnnotatedTotal(TypedDict):
             output=OutputShape(
                 fields=(
                     OutputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         accessor=create_key_accessor("annotated_field", access_error=None),
@@ -463,7 +458,7 @@ class WithAnnotatedTotal(TypedDict):
     )
 
     class WithAnnotatedNotTotal(TypedDict, total=False):
-        annotated_field: typing.Annotated[int, "metadata"]
+        annotated_field: Annotated[int, "metadata"]
 
     assert (
         get_typed_dict_shape(WithAnnotatedNotTotal)
@@ -474,7 +469,7 @@ class WithAnnotatedNotTotal(TypedDict, total=False):
                 kwargs=None,
                 fields=(
                     InputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         is_required=False,
@@ -494,7 +489,7 @@ class WithAnnotatedNotTotal(TypedDict, total=False):
             output=OutputShape(
                 fields=(
                     OutputField(
-                        type=typing.Annotated[int, "metadata"],
+                        type=Annotated[int, "metadata"],
                         id="annotated_field",
                         default=NoDefault(),
                         accessor=create_key_accessor("annotated_field", access_error=KeyError),
@@ -672,13 +667,13 @@ class Child(Base, total=False):
 def test_required_annotated():
     class Base(TypedDict):
         f1: int
-        f2: typing.Annotated[typing.Required[int], "metadata"]
-        f3: 'typing.NotRequired[typing.Annotated[int, "metadata"]]'
+        f2: Annotated[typing.Required[int], "metadata"]
+        f3: 'typing.NotRequired[Annotated[int, "metadata"]]'
 
     class Child(Base, total=False):
         f4: int
-        f5: 'typing.Annotated[typing.Required[int], "metadata"]'
-        f6: typing.NotRequired[typing.Annotated[int, "metadata"]]
+        f5: 'Annotated[typing.Required[int], "metadata"]'
+        f6: typing.NotRequired[Annotated[int, "metadata"]]
 
     assert (
         get_typed_dict_shape(Child)
@@ -697,7 +692,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     InputField(
-                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        type=Annotated[typing.Required[int], "metadata"],
                         id="f2",
                         default=NoDefault(),
                         is_required=True,
@@ -705,7 +700,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     InputField(
-                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        type=typing.NotRequired[Annotated[int, "metadata"]],
                         id="f3",
                         default=NoDefault(),
                         is_required=False,
@@ -721,7 +716,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     InputField(
-                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        type=Annotated[typing.Required[int], "metadata"],
                         id="f5",
                         default=NoDefault(),
                         is_required=True,
@@ -729,7 +724,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     InputField(
-                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        type=typing.NotRequired[Annotated[int, "metadata"]],
                         id="f6",
                         default=NoDefault(),
                         is_required=False,
@@ -782,7 +777,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     OutputField(
-                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        type=Annotated[typing.Required[int], "metadata"],
                         id="f2",
                         default=NoDefault(),
                         accessor=create_key_accessor("f2", access_error=None),
@@ -790,7 +785,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     OutputField(
-                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        type=typing.NotRequired[Annotated[int, "metadata"]],
                         id="f3",
                         default=NoDefault(),
                         accessor=create_key_accessor("f3", access_error=KeyError),
@@ -806,7 +801,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     OutputField(
-                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        type=Annotated[typing.Required[int], "metadata"],
                         id="f5",
                         default=NoDefault(),
                         accessor=create_key_accessor("f5", access_error=None),
@@ -814,7 +809,7 @@ class Child(Base, total=False):
                         original=None,
                     ),
                     OutputField(
-                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        type=typing.NotRequired[Annotated[int, "metadata"]],
                         id="f6",
                         default=NoDefault(),
                         accessor=create_key_accessor("f6", access_error=KeyError),
diff --git a/tests/unit/morphing/facade/test_retort.py b/tests/unit/morphing/facade/test_retort.py
index 0c3a0f76..0ad61d4b 100644
--- a/tests/unit/morphing/facade/test_retort.py
+++ b/tests/unit/morphing/facade/test_retort.py
@@ -27,7 +27,7 @@ def test_retort_extend():
     extended_retort = retort.extend(recipe=to_extend)
 
     assert retort._get_full_recipe() == recipe_before_extend
-    assert extended_retort._get_full_recipe()[:len(to_extend)] == to_extend
+    assert extended_retort._get_full_recipe()[1:len(to_extend)+1] == to_extend
     assert not extended_retort._loader_cache
     assert not extended_retort._dumper_cache
 
diff --git a/tests/unit/morphing/generic_provider/test_literal_provider.py b/tests/unit/morphing/generic_provider/test_literal_provider.py
index 3c132577..c88e80a6 100644
--- a/tests/unit/morphing/generic_provider/test_literal_provider.py
+++ b/tests/unit/morphing/generic_provider/test_literal_provider.py
@@ -1,11 +1,12 @@
 # ruff: noqa: FBT003
 from enum import Enum
-from typing import Literal
+from typing import Any, Iterable, Literal
 from uuid import uuid4
 
+import pytest
 from tests_helpers import raises_exc
 
-from adaptix import Retort
+from adaptix import P, Provider, Retort, dumper, loader
 from adaptix._internal.morphing.load_error import BadVariantLoadError
 
 
@@ -29,11 +30,11 @@ def test_loader_base(strict_coercion, debug_trail):
 
 
 def _is_exact_zero(arg):
-    return type(arg) is int and arg == 0  # noqa: E721
+    return type(arg) is int and arg == 0
 
 
 def _is_exact_one(arg):
-    return type(arg) is int and arg == 1  # noqa: E721
+    return type(arg) is int and arg == 1
 
 
 def test_strict_coercion(debug_trail):
@@ -124,6 +125,66 @@ class Enum2(Enum):
     )
 
 
+@pytest.mark.parametrize(
+    ["input_data", "recipe"],
+    [
+        ("YWJj", []),
+        ("abc", [loader(P[bytes], lambda x: x.encode())]),
+    ],
+)
+def test_loader_with_bytes(
+    strict_coercion,
+    debug_trail,
+    input_data: Any,
+    recipe: Iterable[Provider],
+):
+    retort = Retort(
+        recipe=recipe,
+    )
+
+    loader = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_loader(
+        Literal[b"abc"],
+    )
+
+    assert loader(input_data) == b"abc"
+
+    raises_exc(
+        BadVariantLoadError({b"abc"}, "YWJ"),
+        lambda: loader("YWJ"),
+    )
+
+
+def test_loader_with_bytes_and_enums(strict_coercion, debug_trail):
+    class Enum1(Enum):
+        CASE1 = 1
+        CASE2 = 2
+
+    retort = Retort()
+
+    loader = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_loader(
+        Literal[b"abc", Enum1.CASE1],
+    )
+
+    assert loader("YWJj") == b"abc"
+    assert loader(1) == Enum1.CASE1
+
+    raises_exc(
+        BadVariantLoadError({b"abc", Enum1.CASE1.value}, "YWJ"),
+        lambda: loader("YWJ"),
+    )
+
+    raises_exc(
+        BadVariantLoadError({b"abc", Enum1.CASE1.value}, 2),
+        lambda: loader(2),
+    )
+
+
 def test_dumper_with_enums(strict_coercion, debug_trail):
     retort = Retort()
 
@@ -156,3 +217,46 @@ class Enum2(Enum):
     assert dumper(Enum1.CASE1) == 1
     assert dumper(Enum1.CASE2) == 2
     assert dumper(10) == 10
+
+@pytest.mark.parametrize(
+    ["expected_data", "recipe"],
+    [
+        ("YWJj", []),
+        ("abc", [dumper(P[bytes], lambda x: x.decode())]),
+    ],
+)
+def test_dumper_with_bytes(strict_coercion, debug_trail, expected_data: Any, recipe: Iterable[Provider]):
+    retort = Retort(
+        recipe=recipe,
+    )
+
+    dumper = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_dumper(
+        Literal[b"abc"],
+    )
+
+    assert dumper(b"abc") == expected_data
+
+
+def test_dumper_with_bytes_and_enums(strict_coercion, debug_trail):
+    class Enum1(Enum):
+        CASE1 = 1
+        CASE2 = 2
+
+    class Enum2(Enum):
+        CASE1 = 1
+        CASE2 = 2
+
+    retort = Retort()
+
+    dumper = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_dumper(
+        Literal[b"abc", Enum1.CASE1],
+    )
+
+    assert dumper(b"abc") == "YWJj"
+    assert dumper(Enum1.CASE1) == 1
diff --git a/tests/unit/morphing/model/test_dumper_provider.py b/tests/unit/morphing/model/test_dumper_provider.py
index 7b1d6023..5abd7b30 100644
--- a/tests/unit/morphing/model/test_dumper_provider.py
+++ b/tests/unit/morphing/model/test_dumper_provider.py
@@ -32,8 +32,8 @@
 )
 from adaptix._internal.morphing.model.dumper_provider import ModelDumperProvider
 from adaptix._internal.morphing.request_cls import DumperRequest
-from adaptix._internal.provider.provider_template import ValueProvider
 from adaptix._internal.provider.shape_provider import OutputShapeRequest
+from adaptix._internal.provider.value_provider import ValueProvider
 from adaptix._internal.struct_trail import Attr, TrailElement, TrailElementMarker
 from adaptix._internal.utils import SingletonMeta
 
@@ -820,10 +820,10 @@ def test_optional_fields_at_list(debug_ctx, debug_trail, acc_schema):
         ),
         name_layout=OutputNameLayout(
             crown=OutListCrown(
-                [
+                (
                     OutFieldCrown("a"),
                     OutFieldCrown("b"),
-                ],
+                ),
             ),
             extra_move=None,
         ),
@@ -993,10 +993,10 @@ def test_direct_list(debug_ctx, debug_trail, trail_select, acc_schema):
         ),
         name_layout=OutputNameLayout(
             crown=OutListCrown(
-                [
+                (
                     OutFieldCrown("a"),
                     OutFieldCrown("b"),
-                ],
+                ),
             ),
             extra_move=None,
         ),
@@ -1084,7 +1084,7 @@ def test_structure_flattening(debug_ctx, debug_trail, trail_select, acc_schema):
                     ),
                     "w": OutFieldCrown("c"),
                     "v": OutListCrown(
-                        [
+                        (
                             OutFieldCrown("d"),
                             OutDictCrown(
                                 {
@@ -1093,11 +1093,11 @@ def test_structure_flattening(debug_ctx, debug_trail, trail_select, acc_schema):
                                 sieves={},
                             ),
                             OutListCrown(
-                                [
+                                (
                                     OutFieldCrown("f"),
-                                ],
+                                ),
                             ),
-                        ],
+                        ),
                     ),
                     "t": OutDictCrown(
                         {
@@ -1106,9 +1106,9 @@ def test_structure_flattening(debug_ctx, debug_trail, trail_select, acc_schema):
                         sieves={},
                     ),
                     "r": OutListCrown(
-                        [
+                        (
                             OutFieldCrown("h"),
-                        ],
+                        ),
                     ),
                 },
                 sieves={
@@ -1362,12 +1362,12 @@ def test_none_crown_at_list_crown(debug_ctx, debug_trail, acc_schema):
         ),
         name_layout=OutputNameLayout(
             crown=OutListCrown(
-                [
+                (
                     OutNoneCrown(placeholder=DefaultValue(None)),
                     OutNoneCrown(placeholder=DefaultValue(SomeClass(2))),
                     OutFieldCrown("a"),
                     OutNoneCrown(placeholder=DefaultFactory(list)),
-                ],
+                ),
             ),
             extra_move=None,
         ),
diff --git a/tests/unit/morphing/model/test_loader_provider.py b/tests/unit/morphing/model/test_loader_provider.py
index e9a19624..7c592e75 100644
--- a/tests/unit/morphing/model/test_loader_provider.py
+++ b/tests/unit/morphing/model/test_loader_provider.py
@@ -34,8 +34,8 @@
     InputNameLayoutRequest,
 )
 from adaptix._internal.morphing.request_cls import LoaderRequest
-from adaptix._internal.provider.provider_template import ValueProvider
 from adaptix._internal.provider.shape_provider import InputShapeRequest
+from adaptix._internal.provider.value_provider import ValueProvider
 from adaptix.load_error import (
     ExtraFieldsLoadError,
     ExtraItemsLoadError,
@@ -298,10 +298,10 @@ def test_direct_list(debug_ctx, debug_trail, extra_policy, trail_select, strict_
         ),
         name_layout=InputNameLayout(
             crown=InpListCrown(
-                [
+                (
                     InpFieldCrown("a"),
                     InpFieldCrown("b"),
-                ],
+                ),
                 extra_policy=extra_policy,
             ),
             extra_move=None,
@@ -710,10 +710,10 @@ def test_optional_fields_at_list(debug_ctx, debug_trail, extra_policy):
         ),
         name_layout=InputNameLayout(
             crown=InpListCrown(
-                [
+                (
                     InpFieldCrown("a"),
                     InpFieldCrown("b"),
-                ],
+                ),
                 extra_policy=extra_policy,
             ),
             extra_move=None,
@@ -785,7 +785,7 @@ def test_flat_mapping(debug_ctx, debug_trail, is_required, trail_select):
         ),
         "w": InpFieldCrown("c"),
         "v": InpListCrown(
-            [
+            (
                 InpFieldCrown("d"),
                 InpDictCrown(
                     {
@@ -794,12 +794,12 @@ def test_flat_mapping(debug_ctx, debug_trail, is_required, trail_select):
                     extra_policy=ExtraCollect(),
                 ),
                 InpListCrown(
-                    [
+                    (
                         InpFieldCrown("f"),
-                    ],
+                    ),
                     extra_policy=ExtraForbid(),
                 ),
-            ],
+            ),
             extra_policy=ExtraForbid(),
         ),
     },
@@ -1037,11 +1037,11 @@ def test_none_crown_at_list_crown(debug_ctx, debug_trail, extra_policy, trail_se
         ),
         name_layout=InputNameLayout(
             crown=InpListCrown(
-                [
+                (
                     InpNoneCrown(),
                     InpFieldCrown("a"),
                     InpNoneCrown(),
-                ],
+                ),
                 extra_policy=extra_policy,
             ),
             extra_move=None,
@@ -1184,7 +1184,7 @@ def test_empty_list(debug_ctx, debug_trail, extra_policy, trail_select, strict_c
         shape=shape(),
         name_layout=InputNameLayout(
             crown=InpListCrown(
-                [],
+                (),
                 extra_policy=extra_policy,
             ),
             extra_move=None,
diff --git a/tests/unit/morphing/name_layout/test_provider.py b/tests/unit/morphing/name_layout/test_provider.py
index 69ff1a99..6a1b68d4 100644
--- a/tests/unit/morphing/name_layout/test_provider.py
+++ b/tests/unit/morphing/name_layout/test_provider.py
@@ -54,10 +54,10 @@
     OutputNameLayoutRequest,
 )
 from adaptix._internal.morphing.request_cls import DumperRequest, LoaderRequest
-from adaptix._internal.provider.loc_stack_filtering import P
-from adaptix._internal.provider.provider_template import ValueProvider
-from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
+from adaptix._internal.provider.loc_stack_filtering import LocStack, P
+from adaptix._internal.provider.location import TypeHintLoc
 from adaptix._internal.provider.shape_provider import InputShapeRequest, OutputShapeRequest
+from adaptix._internal.provider.value_provider import ValueProvider
 
 
 @dataclass
@@ -351,20 +351,20 @@ def test_as_list():
     ) == Layouts(
         inp=InputNameLayout(
             crown=InpListCrown(
-                map=[
+                map=(
                     InpFieldCrown(id="a"),
                     InpFieldCrown(id="b"),
-                ],
+                ),
                 extra_policy=ExtraSkip(),
             ),
             extra_move=None,
         ),
         out=OutputNameLayout(
             crown=OutListCrown(
-                map=[
+                map=(
                     OutFieldCrown(id="a"),
                     OutFieldCrown(id="b"),
-                ],
+                ),
             ),
             extra_move=None,
         ),
@@ -385,22 +385,22 @@ def test_as_list():
     ) == Layouts(
         inp=InputNameLayout(
             crown=InpListCrown(
-                map=[
+                map=(
                     InpFieldCrown(id="b"),
                     InpFieldCrown(id="a"),
                     InpFieldCrown(id="c"),
-                ],
+                ),
                 extra_policy=ExtraSkip(),
             ),
             extra_move=None,
         ),
         out=OutputNameLayout(
             crown=OutListCrown(
-                map=[
+                map=(
                     OutFieldCrown(id="b"),
                     OutFieldCrown(id="a"),
                     OutFieldCrown(id="c"),
-                ],
+                ),
             ),
             extra_move=None,
         ),
@@ -544,22 +544,22 @@ def test_gaps_filling():
     ) == Layouts(
         inp=InputNameLayout(
             crown=InpListCrown(
-                map=[
+                map=(
                     InpFieldCrown(id="a"),
                     InpNoneCrown(),
                     InpFieldCrown(id="b"),
-                ],
+                ),
                 extra_policy=ExtraSkip(),
             ),
             extra_move=None,
         ),
         out=OutputNameLayout(
             crown=OutListCrown(
-                map=[
+                map=(
                     OutFieldCrown(id="a"),
                     OutNoneCrown(placeholder=DefaultValue(value=None)),
                     OutFieldCrown(id="b"),
-                ],
+                ),
             ),
             extra_move=None,
         ),
@@ -590,19 +590,19 @@ def test_structure_flattening():
                 map={
                     "f": InpFieldCrown(id="f"),
                     "w": InpListCrown(
-                        map=[
+                        map=(
                             InpFieldCrown(id="d"),
-                        ],
+                        ),
                         extra_policy=ExtraSkip(),
                     ),
                     "x": InpDictCrown(
                         map={
                             "e": InpFieldCrown(id="e"),
                             "y": InpListCrown(
-                                map=[
+                                map=(
                                     InpFieldCrown(id="a"),
                                     InpFieldCrown(id="b"),
-                                ],
+                                ),
                                 extra_policy=ExtraSkip(),
                             ),
                             "z": InpFieldCrown(id="c"),
@@ -619,18 +619,18 @@ def test_structure_flattening():
                 map={
                     "f": OutFieldCrown(id="f"),
                     "w": OutListCrown(
-                        map=[
+                        map=(
                             OutFieldCrown(id="d"),
-                        ],
+                        ),
                     ),
                     "x": OutDictCrown(
                         map={
                             "e": OutFieldCrown(id="e"),
                             "y": OutListCrown(
-                                map=[
+                                map=(
                                     OutFieldCrown(id="a"),
                                     OutFieldCrown(id="b"),
-                                ],
+                                ),
                             ),
                             "z": OutFieldCrown(id="c"),
                         },
@@ -832,18 +832,18 @@ def test_extra_at_list():
     assert layouts == Layouts(
         InputNameLayout(
             crown=InpListCrown(
-                map=[
+                map=(
                     InpFieldCrown("a"),
-                ],
+                ),
                 extra_policy=ExtraSkip(),
             ),
             extra_move=None,
         ),
         OutputNameLayout(
             crown=OutListCrown(
-                map=[
+                map=(
                     OutFieldCrown("a"),
-                ],
+                ),
             ),
             extra_move=None,
         ),
@@ -863,18 +863,18 @@ def test_extra_at_list():
     assert layouts == Layouts(
         InputNameLayout(
             crown=InpListCrown(
-                map=[
+                map=(
                     InpFieldCrown("a"),
-                ],
+                ),
                 extra_policy=ExtraForbid(),
             ),
             extra_move=None,
         ),
         OutputNameLayout(
             crown=OutListCrown(
-                map=[
+                map=(
                     OutFieldCrown("a"),
-                ],
+                ),
             ),
             extra_move=None,
         ),
@@ -1205,9 +1205,9 @@ def test_ellipsis_replacing_int_key():
         TestField("b"),
         name_mapping(
             as_list=True,
-            map=[
+            map=(
                 (".*", ("data", ...)),
-            ],
+            ),
         ),
         DEFAULT_NAME_MAPPING,
     )
@@ -1216,10 +1216,10 @@ def test_ellipsis_replacing_int_key():
             crown=InpDictCrown(
                 map={
                     "data": InpListCrown(
-                        map=[
+                        map=(
                             InpFieldCrown("a_"),
                             InpFieldCrown("b"),
-                        ],
+                        ),
                         extra_policy=ExtraSkip(),
                     ),
                 },
@@ -1231,10 +1231,10 @@ def test_ellipsis_replacing_int_key():
             crown=OutDictCrown(
                 map={
                     "data": OutListCrown(
-                        map=[
+                        map=(
                             OutFieldCrown("a_"),
                             OutFieldCrown("b"),
-                        ],
+                        ),
                     ),
                 },
                 sieves={},
@@ -1274,14 +1274,14 @@ def test_empty_models_list():
     assert layouts == Layouts(
         InputNameLayout(
             crown=InpListCrown(
-                map=[],
+                map=(),
                 extra_policy=ExtraSkip(),
             ),
             extra_move=None,
         ),
         OutputNameLayout(
             crown=OutListCrown(
-                map=[],
+                map=(),
             ),
             extra_move=None,
         ),
diff --git a/tests/unit/morphing/test_concrete_provider.py b/tests/unit/morphing/test_concrete_provider.py
index 17d195e9..60f06c5a 100644
--- a/tests/unit/morphing/test_concrete_provider.py
+++ b/tests/unit/morphing/test_concrete_provider.py
@@ -12,30 +12,112 @@
 
 from adaptix import Retort
 from adaptix._internal.feature_requirement import HAS_PY_311, IS_PYPY
-from adaptix._internal.morphing.concrete_provider import DatetimeFormatProvider
+from adaptix._internal.morphing.concrete_provider import (
+    DatetimeFormatProvider,
+    DateTimestampProvider,
+    DatetimeTimestampProvider,
+)
 from adaptix.load_error import FormatMismatchLoadError, TypeLoadError, ValueLoadError
 
+INVALID_INPUT_ISO_FORMAT = (
+    None,
+    10,
+    datetime(2011, 11, 4, 0, 0),
+    date(2019, 12, 4),
+    time(4, 23, 1),
+)
 
-def check_any_dt(loader):
-    raises_exc(
-        TypeLoadError(str, None),
-        lambda: loader(None),
+INVALID_INPUT_TIMESTAMP = (
+    None,
+    datetime(2011, 11, 4, 0, 0),
+    date(2019, 12, 4),
+    time(4, 23, 1),
+)
+
+
+@pytest.mark.parametrize(
+    "tp",
+    [datetime, date, time],
+)
+@pytest.mark.parametrize(
+    "value",
+    INVALID_INPUT_ISO_FORMAT,
+)
+def test_invalid_input_iso_format(
+    strict_coercion,
+    debug_trail,
+    value,
+    tp,
+):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
     )
+
+    loader = retort.get_loader(tp)
+
     raises_exc(
-        TypeLoadError(str, 10),
-        lambda: loader(10),
+        TypeLoadError(str, value),
+        lambda: loader(value),
     )
-    raises_exc(
-        TypeLoadError(str, datetime(2011, 11, 4, 0, 0)),
-        lambda: loader(datetime(2011, 11, 4, 0, 0)),
+
+
+@pytest.mark.parametrize(
+    "value",
+    INVALID_INPUT_ISO_FORMAT,
+)
+def test_invalid_input_datetime_format(
+    strict_coercion,
+    debug_trail,
+    value,
+):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            DatetimeFormatProvider("%Y-%m-%d"),
+        ],
     )
+
+    loader = retort.get_loader(datetime)
+
     raises_exc(
-        TypeLoadError(str, date(2019, 12, 4)),
-        lambda: loader(date(2019, 12, 4)),
+        TypeLoadError(str, value),
+        lambda: loader(value),
+    )
+
+
+@pytest.mark.parametrize(
+    ["tp", "loader"],
+    [
+        (datetime, DatetimeTimestampProvider(tz=timezone.utc)),
+        (date, DateTimestampProvider()),
+    ],
+)
+@pytest.mark.parametrize(
+    "value",
+    INVALID_INPUT_TIMESTAMP,
+)
+def test_invalid_input_timestamp(
+    strict_coercion,
+    debug_trail,
+    value,
+    tp,
+    loader,
+):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            loader,
+        ],
     )
+
+    loader = retort.get_loader(tp)
+
     raises_exc(
-        TypeLoadError(str, time(4, 23, 1)),
-        lambda: loader(time(4, 23, 1)),
+        TypeLoadError(Union[float, int], value),
+        lambda: loader(value),
     )
 
 
@@ -53,8 +135,6 @@ def test_iso_format_provider_datetime(strict_coercion, debug_trail):
         tzinfo=timezone(timedelta(seconds=14400)),
     )
 
-    check_any_dt(loader)
-
     raises_exc(
         ValueLoadError("Invalid isoformat string", "some string"),
         lambda: loader("some string"),
@@ -72,7 +152,6 @@ def test_iso_format_provider_date(strict_coercion, debug_trail):
 
     loader = retort.get_loader(date)
     assert loader("2019-12-04") == date(2019, 12, 4)
-    check_any_dt(loader)
 
     raises_exc(
         ValueLoadError("Invalid isoformat string", "some string"),
@@ -95,7 +174,6 @@ def test_iso_format_provider_time(strict_coercion, debug_trail):
         4, 23, 1,
         tzinfo=timezone(timedelta(seconds=14400)),
     )
-    check_any_dt(loader)
 
     raises_exc(
         ValueLoadError("Invalid isoformat string", "some string"),
@@ -118,8 +196,6 @@ def test_datetime_format_provider(strict_coercion, debug_trail):
     loader = retort.get_loader(datetime)
     assert loader("3045-02-13") == datetime(year=3045, month=2, day=13)
 
-    check_any_dt(loader)
-
     raises_exc(
         FormatMismatchLoadError("%Y-%m-%d", "some string"),
         lambda: loader("some string"),
@@ -129,6 +205,78 @@ def test_datetime_format_provider(strict_coercion, debug_trail):
     assert dumper(datetime(year=3045, month=2, day=13)) == "3045-02-13"
 
 
+@pytest.mark.parametrize(
+    "tz",
+    [
+        None,
+        timezone(timedelta(hours=3)),
+    ],
+)
+def test_datetime_timestamp_provider(strict_coercion, debug_trail, tz: timezone):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            DatetimeTimestampProvider(tz=tz),
+        ],
+    )
+
+    loader = retort.get_loader(datetime)
+
+    dt = datetime(2011, 11, 4, 6, 38, tzinfo=tz)
+    ts = dt.timestamp()
+
+    assert loader(ts) == dt
+
+    overflow_ts = float("inf")
+    raises_exc(
+        ValueLoadError("Timestamp is out of the range of supported values", overflow_ts),
+        lambda: loader(overflow_ts),
+    )
+
+    nan = float("nan")
+    raises_exc(
+        ValueLoadError("Unexpected value", nan),
+        lambda: loader(nan),
+    )
+
+    dumper = retort.get_dumper(datetime)
+    assert dumper(dt) == ts
+
+
+def test_date_timestamp_provider(strict_coercion, debug_trail):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            DateTimestampProvider(),
+        ],
+    )
+
+    loader = retort.get_loader(date)
+    dt = datetime(2011, 11, 4, tzinfo=timezone.utc)
+    today = dt.date()
+
+    ts = dt.timestamp()
+
+    assert loader(ts) == today
+
+    overflow_ts = float("inf")
+    raises_exc(
+        ValueLoadError("Timestamp is out of the range of supported values", overflow_ts),
+        lambda: loader(overflow_ts),
+    )
+
+    nan = float("nan")
+    raises_exc(
+        ValueLoadError("Unexpected value", nan),
+        lambda: loader(nan),
+    )
+
+    dumper = retort.get_dumper(date)
+    assert dumper(dt) == ts
+
+
 def test_seconds_timedelta_provider(strict_coercion, debug_trail):
     retort = Retort(
         strict_coercion=strict_coercion,
diff --git a/tests/unit/morphing/test_constant_length_tuple_provider.py b/tests/unit/morphing/test_constant_length_tuple_provider.py
index e1b44e70..1b21c543 100644
--- a/tests/unit/morphing/test_constant_length_tuple_provider.py
+++ b/tests/unit/morphing/test_constant_length_tuple_provider.py
@@ -1,7 +1,8 @@
 import collections
 import collections.abc
 import typing
-from typing import Mapping, Tuple
+from collections.abc import Mapping
+from typing import Tuple
 
 import pytest
 from tests_helpers import raises_exc, requires, with_trail
@@ -9,7 +10,7 @@
 from adaptix import AdornedRetort, DebugTrail, ProviderNotFoundError, dumper, loader
 from adaptix._internal.compat import CompatExceptionGroup
 from adaptix._internal.feature_requirement import HAS_UNPACK
-from adaptix._internal.morphing.concrete_provider import INT_LOADER_PROVIDER, STR_LOADER_PROVIDER
+from adaptix._internal.morphing.concrete_provider import INT_PROVIDER, STR_PROVIDER
 from adaptix._internal.morphing.constant_length_tuple_provider import ConstantLengthTupleProvider
 from adaptix._internal.morphing.load_error import AggregateLoadError
 from adaptix.load_error import ExcludedTypeLoadError, ExtraItemsLoadError, NoRequiredItemsLoadError, TypeLoadError
@@ -32,7 +33,7 @@ def retort():
     return AdornedRetort(
         recipe=[
             ConstantLengthTupleProvider(),
-            STR_LOADER_PROVIDER,
+            STR_PROVIDER,
             dumper(str, string_dumper),
         ],
     )
@@ -188,7 +189,7 @@ def test_dumping(retort, debug_trail):
         )
     elif debug_trail == DebugTrail.FIRST:
         raises_exc(
-            with_trail(TypeError(), [0]),
+            with_trail(TypeError(), [1]),
             lambda: second_dumper([10, "20"]),
         )
         raises_exc(
@@ -200,7 +201,6 @@ def test_dumping(retort, debug_trail):
             CompatExceptionGroup(
                 "while dumping tuple",
                 [
-                    with_trail(TypeError(), [0]),
                     with_trail(TypeError(), [1]),
                 ],
             ),
@@ -211,7 +211,6 @@ def test_dumping(retort, debug_trail):
                 "while dumping tuple",
                 [
                     with_trail(TypeError(), [0]),
-                    with_trail(TypeError(), [1]),
                 ],
             ),
             lambda: third_dumper(["10", 20]),
@@ -221,7 +220,7 @@ def test_dumping(retort, debug_trail):
 def test_loading_not_enough_fields(retort):
     retort = retort.extend(
         recipe=[
-            INT_LOADER_PROVIDER,
+            INT_PROVIDER,
         ],
     )
 
@@ -258,7 +257,7 @@ def test_dumping_not_enough_fields(retort):
 def test_unpack_loading(retort):
     retort = retort.extend(
         recipe=[
-            INT_LOADER_PROVIDER,
+            INT_PROVIDER,
         ],
     )
     with pytest.raises(ProviderNotFoundError):
diff --git a/tests/unit/morphing/test_enum_provider.py b/tests/unit/morphing/test_enum_provider.py
index 7540b02c..3b345fbf 100644
--- a/tests/unit/morphing/test_enum_provider.py
+++ b/tests/unit/morphing/test_enum_provider.py
@@ -1,11 +1,13 @@
+from collections.abc import Iterable, Mapping
 from enum import Enum, Flag, IntEnum, auto
-from typing import Iterable, Mapping, Union
+from typing import Union
 
 import pytest
 from tests_helpers import parametrize_bool, raises_exc, with_cause, with_notes
 
 from adaptix import (
     CannotProvide,
+    DebugTrail,
     NameStyle,
     ProviderNotFoundError,
     Retort,
@@ -14,7 +16,6 @@
     enum_by_value,
     flag_by_member_names,
 )
-from adaptix._internal.morphing.enum_provider import EnumExactValueProvider
 from adaptix._internal.morphing.load_error import (
     DuplicatedValuesLoadError,
     ExcludedTypeLoadError,
@@ -163,9 +164,13 @@ def test_exact_value_provider_int_enum(strict_coercion, debug_trail):
     )
 
 
-def test_exact_value_optimization(strict_coercion, debug_trail):
-    assert EnumExactValueProvider()._make_loader(MyEnum).__name__ == "enum_exact_loader_v2m"
-    assert EnumExactValueProvider()._make_loader(MyEnumWithMissingHook).__name__ == "enum_exact_loader"
+def test_exact_value_optimization(strict_coercion):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=DebugTrail.DISABLE,
+    )
+    assert retort.get_loader(MyEnum).__name__ == "enum_exact_loader_v2m"
+    assert retort.get_loader(MyEnumWithMissingHook).__name__ == "enum_exact_loader"
 
 
 def custom_string_dumper(value: str):
diff --git a/tests/unit/morphing/test_iterable_provider.py b/tests/unit/morphing/test_iterable_provider.py
index b6e05ac2..7957eb00 100644
--- a/tests/unit/morphing/test_iterable_provider.py
+++ b/tests/unit/morphing/test_iterable_provider.py
@@ -1,15 +1,14 @@
 import collections
 import collections.abc
 from collections import deque
+from collections.abc import Iterable, Mapping
 from typing import (
     AbstractSet,
     Collection,
     Deque,
     Dict,
     FrozenSet,
-    Iterable,
     List,
-    Mapping,
     MutableSequence,
     MutableSet,
     Reversible,
diff --git a/tests/unit/provider/shape_provider/data_gen_models.py b/tests/unit/provider/shape_provider/data_gen_models.py
index b1710a90..8d938e74 100644
--- a/tests/unit/provider/shape_provider/data_gen_models.py
+++ b/tests/unit/provider/shape_provider/data_gen_models.py
@@ -1,5 +1,5 @@
 # mypy: disable-error-code="name-defined, misc"
-from typing import Generic, Tuple, TypeVar
+from typing import Generic, TypeVar
 
 from tests_helpers import ModelSpec
 
@@ -24,16 +24,16 @@ class WithTVField(*model_spec.bases, Generic[_T]):
 
     @model_spec.decorator
     class WithTVTupleBegin(*model_spec.bases, Generic[Unpack[ShapeT], T]):
-        a: Tuple[Unpack[ShapeT]]
+        a: tuple[Unpack[ShapeT]]
         b: T
 
     @model_spec.decorator
     class WithTVTupleEnd(*model_spec.bases, Generic[T, Unpack[ShapeT]]):
         a: T
-        b: Tuple[Unpack[ShapeT]]
+        b: tuple[Unpack[ShapeT]]
 
     @model_spec.decorator
     class WithTVTupleMiddle(*model_spec.bases, Generic[T1, Unpack[ShapeT], T2]):
         a: T1
-        b: Tuple[Unpack[ShapeT]]
+        b: tuple[Unpack[ShapeT]]
         c: T2
diff --git a/tests/unit/provider/shape_provider/data_gen_models_312.py b/tests/unit/provider/shape_provider/data_gen_models_312.py
index 08575b74..165a404b 100644
--- a/tests/unit/provider/shape_provider/data_gen_models_312.py
+++ b/tests/unit/provider/shape_provider/data_gen_models_312.py
@@ -1,4 +1,4 @@
-from typing import Tuple, Unpack
+from typing import Unpack
 
 from tests_helpers import ModelSpec
 
@@ -12,18 +12,18 @@ class WithTVField[_T](*model_spec.bases):
 if model_spec.kind != ModelSpec.PYDANTIC:
     @model_spec.decorator
     class WithTVTupleBegin[*ShapeT, T](*model_spec.bases):
-        a: Tuple[Unpack[ShapeT]]
+        a: tuple[Unpack[ShapeT]]
         b: T
 
 
     @model_spec.decorator
     class WithTVTupleEnd[T, *ShapeT](*model_spec.bases):
         a: T
-        b: Tuple[Unpack[ShapeT]]
+        b: tuple[Unpack[ShapeT]]
 
 
     @model_spec.decorator
     class WithTVTupleMiddle[T1, *ShapeT, T2](*model_spec.bases):
         a: T1
-        b: Tuple[Unpack[ShapeT]]
+        b: tuple[Unpack[ShapeT]]
         c: T2
diff --git a/tests/unit/provider/shape_provider/local_helpers.py b/tests/unit/provider/shape_provider/local_helpers.py
index 7a68c768..c9ab7722 100644
--- a/tests/unit/provider/shape_provider/local_helpers.py
+++ b/tests/unit/provider/shape_provider/local_helpers.py
@@ -1,7 +1,11 @@
-from typing import Mapping, Optional
+from collections.abc import Mapping
+from typing import Optional
 
-from adaptix import Retort, TypeHint
-from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
+from tests_helpers.misc import create_mediator
+
+from adaptix import TypeHint
+from adaptix._internal.provider.loc_stack_filtering import LocStack
+from adaptix._internal.provider.location import TypeHintLoc
 from adaptix._internal.provider.shape_provider import (
     InputShapeRequest,
     OutputShapeRequest,
@@ -16,21 +20,17 @@ def assert_distinct_fields_types(
     input: Mapping[str, TypeHint],  # noqa: A002
     output: Mapping[str, TypeHint],
 ) -> None:
-    retort = Retort()
-    mediator = retort._create_mediator()
-
     input_shape = provide_generic_resolved_shape(
-        mediator,
+        create_mediator(),
         InputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
     )
-    input_field_types = {field.id: field.type for field in input_shape.fields}
-    assert input_field_types == input
-
     output_shape = provide_generic_resolved_shape(
-        mediator,
+        create_mediator(),
         OutputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
     )
+    input_field_types = {field.id: field.type for field in input_shape.fields}
     output_field_types = {field.id: field.type for field in output_shape.fields}
+    assert input_field_types == input
     assert output_field_types == output
 
 
diff --git a/tests/unit/provider/shape_provider/test_generic_resolving.py b/tests/unit/provider/shape_provider/test_generic_resolving.py
index 92f9c120..38d975bc 100644
--- a/tests/unit/provider/shape_provider/test_generic_resolving.py
+++ b/tests/unit/provider/shape_provider/test_generic_resolving.py
@@ -3,21 +3,20 @@
 
 import pytest
 from tests_helpers import ModelSpec, cond_list, exclude_model_spec, load_namespace_keeping_module, requires
+from tests_helpers.misc import create_mediator
 from tests_helpers.model_spec import only_generic_models, with_model_spec_requirement
 
-from adaptix import CannotProvide, Retort
+from adaptix import CannotProvide
 from adaptix._internal.feature_requirement import (
-    HAS_PY_39,
-    HAS_PY_310,
     HAS_PY_312,
     HAS_SELF_TYPE,
-    HAS_STD_CLASSES_GENERICS,
     HAS_SUPPORTED_PYDANTIC_PKG,
     HAS_TV_TUPLE,
     IS_PYPY,
     DistributionVersionRequirement,
 )
-from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
+from adaptix._internal.provider.loc_stack_filtering import LocStack
+from adaptix._internal.provider.location import TypeHintLoc
 from adaptix._internal.provider.shape_provider import (
     InputShapeRequest,
     OutputShapeRequest,
@@ -67,7 +66,7 @@ def test_type_var_field(gen_models_ns):
     assert_fields_types(WithTVField[T], {"a": int, "b": T})
 
 
-@pytest.mark.parametrize("tp", [List, list] if HAS_STD_CLASSES_GENERICS else [List])
+@pytest.mark.parametrize("tp", [List, list])
 def test_gen_field(model_spec, tp):
     @model_spec.decorator
     class WithGenField(*model_spec.bases, Generic[T]):
@@ -80,8 +79,8 @@ class WithGenField(*model_spec.bases, Generic[T]):
     assert_fields_types(WithGenField[T], {"a": int, "b": tp[T]}, pydantic={"a": int, "b": tp[Any]})
 
 
-@pytest.mark.parametrize("tp1", [List, list] if HAS_STD_CLASSES_GENERICS else [List])
-@pytest.mark.parametrize("tp2", [Dict, dict] if HAS_STD_CLASSES_GENERICS else [Dict])
+@pytest.mark.parametrize("tp1", [List, list])
+@pytest.mark.parametrize("tp2", [Dict, dict])
 def test_two_params(model_spec, tp1, tp2):
     @model_spec.decorator
     class WithStdGenField(*model_spec.bases, Generic[K, V]):
@@ -223,7 +222,7 @@ class Child(Parent1[int], Parent2[bool], Generic[T]):
 
 
 @exclude_model_spec(ModelSpec.NAMED_TUPLE, ModelSpec.ATTRS)
-@pytest.mark.parametrize("tp", [List, list] if HAS_STD_CLASSES_GENERICS else [List])
+@pytest.mark.parametrize("tp", [List, list])
 def test_generic_multiple_inheritance(model_spec, tp) -> None:
     @model_spec.decorator
     class GrandParent(*model_spec.bases, Generic[T1, T2]):
@@ -256,40 +255,30 @@ class Child(Parent1[int, bool], Parent2[str, bytes], Generic[T7]):
     )
 
 
-# TODO: fix it  # noqa: TD003
-skip_if_pypy_39_or_310 = pytest.mark.skipif(
-    IS_PYPY and (HAS_PY_39 or HAS_PY_310),
-    reason="At this python version and implementation list has __init__ that allow to generate Shape",
-)
-
-
 @pytest.mark.parametrize(
     "tp",
     [
         int,
-        pytest.param(list, id="list", marks=skip_if_pypy_39_or_310),
-        pytest.param(List, id="List", marks=skip_if_pypy_39_or_310),
-        pytest.param(List[T], id="List[T]", marks=skip_if_pypy_39_or_310),
-        pytest.param(List[int], id="List[int]", marks=skip_if_pypy_39_or_310),
-        *cond_list(
-            HAS_STD_CLASSES_GENERICS,
-            lambda: [pytest.param(list[T], id="list[T]", marks=skip_if_pypy_39_or_310)],
-        ),
+        list,
+        List,
+        List[T],
+        List[int],
+        list[T],
     ],
 )
 def test_not_a_model(tp):
-    retort = Retort()
-    mediator = retort._create_mediator()
-
-    with pytest.raises(CannotProvide):
-        provide_generic_resolved_shape(
-            mediator,
-            InputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
-        )
+    # TODO: fix it  # noqa: TD003
+    # At this python versions and implementation list has __init__ that allow to generate Shape
+    if not IS_PYPY:
+        with pytest.raises(CannotProvide):
+            provide_generic_resolved_shape(
+                create_mediator(),
+                InputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
+            )
 
     with pytest.raises(CannotProvide):
         provide_generic_resolved_shape(
-            mediator,
+            create_mediator(),
             OutputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
         )
 
@@ -394,35 +383,35 @@ def test_type_var_tuple_begin(model_spec, gen_models_ns):
     assert_fields_types(
         WithTVTupleBegin,
         {
-            "a": Tuple[Unpack[Tuple[Any, ...]]],
+            "a": tuple[Unpack[tuple[Any, ...]]],
             "b": Any,
         },
     )
     assert_fields_types(
         WithTVTupleBegin[int, str],
         {
-            "a": Tuple[int],
+            "a": tuple[int],
             "b": str,
         },
     )
     assert_fields_types(
         WithTVTupleBegin[int, str, bool],
         {
-            "a": Tuple[int, str],
+            "a": tuple[int, str],
             "b": bool,
         },
     )
     assert_fields_types(
         WithTVTupleBegin[int, Unpack[Tuple[str, bool]]],
         {
-            "a": Tuple[int, str],
+            "a": tuple[int, str],
             "b": bool,
         },
     )
     assert_fields_types(
         WithTVTupleBegin[Unpack[Tuple[str, bool]], Unpack[Tuple[str, bool]]],
         {
-            "a": Tuple[str, bool, str],
+            "a": tuple[str, bool, str],
             "b": bool,
         },
     )
@@ -439,35 +428,35 @@ def test_type_var_tuple_end(model_spec, gen_models_ns):
         WithTVTupleEnd,
         {
             "a": Any,
-            "b": Tuple[Unpack[Tuple[Any, ...]]],
+            "b": tuple[Unpack[tuple[Any, ...]]],
         },
     )
     assert_fields_types(
         WithTVTupleEnd[int, str],
         {
             "a": int,
-            "b": Tuple[str],
+            "b": tuple[str],
         },
     )
     assert_fields_types(
         WithTVTupleEnd[int, str, bool],
         {
             "a": int,
-            "b": Tuple[str, bool],
+            "b": tuple[str, bool],
         },
     )
     assert_fields_types(
         WithTVTupleEnd[int, Unpack[Tuple[str, bool]]],
         {
             "a": int,
-            "b": Tuple[str, bool],
+            "b": tuple[str, bool],
         },
     )
     assert_fields_types(
         WithTVTupleEnd[Unpack[Tuple[str, bool]], Unpack[Tuple[str, bool]]],
         {
             "a": str,
-            "b": Tuple[bool, str, bool],
+            "b": tuple[bool, str, bool],
         },
     )
 
@@ -483,7 +472,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle,
         {
             "a": Any,
-            "b": Tuple[Unpack[Tuple[Any, ...]]],
+            "b": tuple[Unpack[tuple[Any, ...]]],
             "c": Any,
         },
     )
@@ -491,7 +480,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle[int, str],
         {
             "a": int,
-            "b": Tuple[()],
+            "b": tuple[()],
             "c": str,
         },
     )
@@ -499,7 +488,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle[int, str, bool],
         {
             "a": int,
-            "b": Tuple[str],
+            "b": tuple[str],
             "c": bool,
         },
     )
@@ -507,7 +496,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle[int, str, str, bool],
         {
             "a": int,
-            "b": Tuple[str, str],
+            "b": tuple[str, str],
             "c": bool,
         },
     )
@@ -515,7 +504,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle[int, Unpack[Tuple[str, bool]]],
         {
             "a": int,
-            "b": Tuple[str],
+            "b": tuple[str],
             "c": bool,
         },
     )
@@ -523,7 +512,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle[int, Unpack[Tuple[str, bool]], int],
         {
             "a": int,
-            "b": Tuple[str, bool],
+            "b": tuple[str, bool],
             "c": int,
         },
     )
@@ -531,7 +520,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle[int, Unpack[Tuple[str, ...]], int],
         {
             "a": int,
-            "b": Tuple[Unpack[Tuple[str, ...]]],
+            "b": tuple[Unpack[Tuple[str, ...]]],
             "c": int,
         },
     )
@@ -539,7 +528,7 @@ def test_type_var_tuple_middle(model_spec, gen_models_ns):
         WithTVTupleMiddle[int, bool, Unpack[Tuple[str, ...]], int],
         {
             "a": int,
-            "b": Tuple[bool, Unpack[Tuple[str, ...]]],
+            "b": tuple[bool, Unpack[Tuple[str, ...]]],
             "c": int,
         },
     )
diff --git a/tests/unit/provider/test_loc_stack_filtering.py b/tests/unit/provider/test_loc_stack_filtering.py
index 67c60cdf..ef0034d9 100644
--- a/tests/unit/provider/test_loc_stack_filtering.py
+++ b/tests/unit/provider/test_loc_stack_filtering.py
@@ -1,26 +1,25 @@
 # ruff: noqa: A001, A002
 import collections.abc
-import typing
 from contextlib import nullcontext
 from dataclasses import dataclass
-from typing import Any, Dict, Generic, Iterable, List, Optional, Type, TypeVar, Union, overload
+from typing import Annotated, Any, Dict, Generic, Iterable, List, Optional, Type, TypeVar, Union, overload
 
 import pytest
-from tests_helpers import cond_list, full_match
+from tests_helpers import full_match
+from tests_helpers.misc import create_mediator
 
 from adaptix import Chain, P, Retort, loader
 from adaptix._internal.common import TypeHint
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
 from adaptix._internal.model_tools.definitions import NoDefault
 from adaptix._internal.provider.loc_stack_filtering import (
     ExactOriginLSC,
     ExactTypeLSC,
+    LocStack,
     LocStackEndChecker,
     OriginSubclassLSC,
     create_loc_stack_checker,
 )
 from adaptix._internal.provider.location import FieldLoc, GenericParamLoc, TypeHintLoc
-from adaptix._internal.provider.request_cls import LocStack
 from adaptix._internal.type_tools import normalize_type
 
 
@@ -56,10 +55,6 @@ def param_result(*values, result=None, raises=None, exact_match=None, match=None
     return pytest.param(*values, result, context, id=id)
 
 
-def create_mediator():
-    return Retort()._create_mediator()
-
-
 @pytest.mark.parametrize(
     ["loc_stack", "result", "context"],
     [
@@ -343,46 +338,41 @@ class MyGeneric(Generic[T]):
             Union,
             result=ExactOriginLSC(Union),
         ),
-        *cond_list(
-            HAS_ANNOTATED,
-            lambda: [
-                param_result(
-                    typing.Annotated,
-                    result=ExactOriginLSC(typing.Annotated),
-                ),
-                param_result(
-                    typing.Annotated[int, "meta"],
-                    result=ExactTypeLSC(normalize_type(typing.Annotated[int, "meta"])),
-                ),
-                param_result(
-                    typing.Annotated[List[int], "meta"],
-                    result=ExactTypeLSC(normalize_type(typing.Annotated[list[int], "meta"])),
-                ),
-                param_result(
-                    typing.Annotated[list, "meta"],
-                    raises=ValueError,
-                    exact_match=(
-                        "Can not create LocStackChecker from"
-                        " typing.Annotated[list, 'meta'] generic alias (parametrized generic)"
-                    ),
-                ),
-                param_result(
-                    typing.Annotated[List[T], "meta"],
-                    raises=ValueError,
-                    exact_match=(
-                        "Can not create LocStackChecker from"
-                        " typing.Annotated[typing.List[~T], 'meta'] generic alias (parametrized generic)"
-                    ),
-                ),
-                param_result(
-                    typing.Annotated[Dict[int, T], "meta"],
-                    raises=ValueError,
-                    exact_match=(
-                        "Can not create LocStackChecker from"
-                        " typing.Annotated[typing.Dict[int, ~T], 'meta'] generic alias (parametrized generic)"
-                    ),
-                ),
-            ],
+        param_result(
+            Annotated,
+            result=ExactOriginLSC(Annotated),
+        ),
+        param_result(
+            Annotated[int, "meta"],
+            result=ExactTypeLSC(normalize_type(Annotated[int, "meta"])),
+        ),
+        param_result(
+            Annotated[List[int], "meta"],
+            result=ExactTypeLSC(normalize_type(Annotated[list[int], "meta"])),
+        ),
+        param_result(
+            Annotated[list, "meta"],
+            raises=ValueError,
+            exact_match=(
+                "Can not create LocStackChecker from"
+                " typing.Annotated[list, 'meta'] generic alias (parametrized generic)"
+            ),
+        ),
+        param_result(
+            Annotated[List[T], "meta"],
+            raises=ValueError,
+            exact_match=(
+                "Can not create LocStackChecker from"
+                " typing.Annotated[typing.List[~T], 'meta'] generic alias (parametrized generic)"
+            ),
+        ),
+        param_result(
+            Annotated[Dict[int, T], "meta"],
+            raises=ValueError,
+            exact_match=(
+                "Can not create LocStackChecker from"
+                " typing.Annotated[typing.Dict[int, ~T], 'meta'] generic alias (parametrized generic)"
+            ),
         ),
     ],
 )
diff --git a/tests/unit/provider/test_methods_provider.py b/tests/unit/provider/test_methods_provider.py
new file mode 100644
index 00000000..013e368c
--- /dev/null
+++ b/tests/unit/provider/test_methods_provider.py
@@ -0,0 +1,107 @@
+from abc import ABC, abstractmethod
+
+import pytest
+
+from adaptix import Mediator, Request
+from adaptix._internal.provider.methods_provider import MethodsProvider, method_handler
+from adaptix._internal.provider.request_checkers import AlwaysTrueRequestChecker
+
+
+class SampleRequest(Request):
+    pass
+
+
+def test_simple():
+    class TestSimple1(MethodsProvider):
+        @method_handler
+        def provide_sample(self, mediator: Mediator, request: SampleRequest):
+            pass
+
+    instance = TestSimple1()
+    assert (
+        instance.get_request_handlers() == [
+            (SampleRequest, AlwaysTrueRequestChecker(), instance.provide_sample),
+        ]
+    )
+
+
+def test_abstract_method():
+    class Base(MethodsProvider, ABC):
+        @abstractmethod
+        @method_handler
+        def provide_sample(self, mediator: Mediator, request: SampleRequest):
+            pass
+
+    class Child(Base):
+        def provide_sample(self, mediator: Mediator, request: SampleRequest):
+            pass
+
+    instance = Child()
+    assert (
+        instance.get_request_handlers() == [
+            (SampleRequest, AlwaysTrueRequestChecker(), instance.provide_sample),
+        ]
+    )
+
+
+def test_error_raising_with_one_class():
+    with pytest.raises(TypeError):
+        class BadDecoratorArg(MethodsProvider):
+            @method_handler
+            def provide(self, mediator: Mediator, request: int):
+                pass
+
+    with pytest.raises(TypeError):
+        class SeveralSPA(MethodsProvider):
+            @method_handler
+            def provide_one(self, mediator: Mediator, request: Request):
+                pass
+
+            @method_handler
+            def provide_two(self, mediator: Mediator, request: Request):
+                pass
+
+
+class Base1(MethodsProvider):
+    @method_handler
+    def provide_one(self, mediator: Mediator, request: Request):
+        pass
+
+
+class Base2(MethodsProvider):
+    @method_handler
+    def provide_two(self, mediator: Mediator, request: Request):
+        pass
+
+
+def test_inheritance_several_spa():
+    with pytest.raises(TypeError):
+        class SeveralSPAChild(Base1):
+            @method_handler
+            def provide_two(self, mediator: Mediator, request: Request):
+                pass
+
+    with pytest.raises(TypeError):
+        class Child12(Base1, Base2):
+            pass
+
+    with pytest.raises(TypeError):
+        class Child21(Base2, Base1):
+            pass
+
+
+def test_inheritance_several_rc():
+    class Base3(MethodsProvider):
+        @method_handler
+        def provide_one(self, mediator: Mediator, request: SampleRequest):
+            pass
+
+    with pytest.raises(TypeError):
+        class Child13(Base1, Base3):
+            pass
+
+    with pytest.raises(TypeError):
+        class Child1(Base1):
+            @method_handler
+            def provide_one(self, mediator: Mediator, request: SampleRequest):
+                pass
diff --git a/tests/unit/provider/test_overlay_schema.py b/tests/unit/provider/test_overlay_schema.py
index 17a919fa..8dde5829 100644
--- a/tests/unit/provider/test_overlay_schema.py
+++ b/tests/unit/provider/test_overlay_schema.py
@@ -6,9 +6,10 @@
 
 from adaptix import AdornedRetort, Chain, Mediator, Omittable, Omitted, Provider, Request, bound
 from adaptix._internal.common import VarTuple
+from adaptix._internal.provider.loc_stack_filtering import LocStack
+from adaptix._internal.provider.location import TypeHintLoc
+from adaptix._internal.provider.methods_provider import MethodsProvider, method_handler
 from adaptix._internal.provider.overlay_schema import Overlay, OverlayProvider, Schema, provide_schema
-from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
-from adaptix._internal.provider.static_provider import StaticProvider, static_provision_action
 
 
 @dataclass(frozen=True)
@@ -31,11 +32,11 @@ class SampleRequest(Request):
     pass
 
 
-class SampleRequestProvider(StaticProvider):
+class SampleRequestProvider(MethodsProvider):
     def __init__(self, provide_action: Callable[[Mediator], MySchema]):
         self.provide_action = provide_action
 
-    @static_provision_action
+    @method_handler
     def _provide_overlay(self, mediator: Mediator, request: SampleRequest):
         return self.provide_action(mediator)
 
diff --git a/tests/unit/provider/test_static_provider.py b/tests/unit/provider/test_static_provider.py
deleted file mode 100644
index a57a7920..00000000
--- a/tests/unit/provider/test_static_provider.py
+++ /dev/null
@@ -1,170 +0,0 @@
-from abc import ABC, abstractmethod
-
-import pytest
-from tests_helpers import full_match
-
-from adaptix import Mediator, Request
-from adaptix._internal.provider.static_provider import RequestDispatcher, StaticProvider, static_provision_action
-
-
-class SampleRequest(Request):
-    pass
-
-
-def test_simple():
-    class TestSimple1(StaticProvider):
-        @static_provision_action(SampleRequest)
-        def _provide_sample(self, mediator: Mediator, request: SampleRequest):
-            pass
-
-    assert (
-        TestSimple1._sp_cls_request_dispatcher
-        ==
-        RequestDispatcher({SampleRequest: "_provide_sample"})
-    )
-
-    class TestSimple2(StaticProvider):
-        @static_provision_action()
-        def _provide_sample(self, mediator: Mediator, request: SampleRequest):
-            pass
-
-    assert (
-        TestSimple2._sp_cls_request_dispatcher
-        ==
-        RequestDispatcher({SampleRequest: "_provide_sample"})
-    )
-
-    class TestSimple3(StaticProvider):
-        @static_provision_action
-        def _provide_sample(self, mediator: Mediator, request: SampleRequest):
-            pass
-
-    assert (
-        TestSimple3._sp_cls_request_dispatcher
-        ==
-        RequestDispatcher({SampleRequest: "_provide_sample"})
-    )
-
-    class TestSimple4(StaticProvider):
-        @static_provision_action(SampleRequest)
-        def _provide_sample(self, mediator: Mediator, request):
-            pass
-
-    assert (
-        TestSimple4._sp_cls_request_dispatcher
-        ==
-        RequestDispatcher({SampleRequest: "_provide_sample"})
-    )
-
-    class NotASampleRequest(Request):
-        pass
-
-    class TestSimple5(StaticProvider):
-        @static_provision_action(SampleRequest)
-        def _provide_sample(self, mediator: Mediator, request: NotASampleRequest):
-            pass
-
-    assert (
-        TestSimple5._sp_cls_request_dispatcher
-        ==
-        RequestDispatcher({SampleRequest: "_provide_sample"})
-    )
-
-
-def test_abstract_method():
-    class Base(StaticProvider, ABC):
-        @abstractmethod
-        @static_provision_action
-        def _provide_sample(self, mediator: Mediator, request: SampleRequest):
-            pass
-
-    class Child(Base):
-        def _provide_sample(self, mediator: Mediator, request: SampleRequest):
-            pass
-
-    assert (
-        Child._sp_cls_request_dispatcher
-        ==
-        RequestDispatcher({SampleRequest: "_provide_sample"})
-    )
-
-
-def test_error_raising_with_one_class():
-    with pytest.raises(TypeError):
-        class BadDecoratorArg(StaticProvider):
-            @static_provision_action
-            def _provide(self, mediator: Mediator, request: int):
-                pass
-
-    with pytest.raises(
-        ValueError,
-        match=full_match("@static_provision_action decorator cannot be applied twice"),
-    ):
-        class DoubleDecoration(StaticProvider):
-            @static_provision_action
-            @static_provision_action
-            def _provide(self, mediator: Mediator, request: Request):
-                pass
-
-    with pytest.raises(TypeError):
-        class SeveralSPA(StaticProvider):
-            @static_provision_action
-            def _provide_one(self, mediator: Mediator, request: Request):
-                pass
-
-            @static_provision_action
-            def _provide_two(self, mediator: Mediator, request: Request):
-                pass
-
-
-class Base1(StaticProvider):
-    @static_provision_action
-    def _provide_one(self, mediator: Mediator, request: Request):
-        pass
-
-
-class Base2(StaticProvider):
-    @static_provision_action
-    def _provide_two(self, mediator: Mediator, request: Request):
-        pass
-
-
-def test_inheritance_redefine_spa():
-    with pytest.raises(TypeError):
-        class RedefineSPAChild(Base1):
-            @static_provision_action
-            def _provide_one(self, mediator: Mediator, request: Request):
-                pass
-
-
-def test_inheritance_several_spa():
-    with pytest.raises(TypeError):
-        class SeveralSPAChild(Base1):
-            @static_provision_action
-            def _provide_two(self, mediator: Mediator, request: Request):
-                pass
-
-    with pytest.raises(TypeError):
-        class Child12(Base1, Base2):
-            pass
-
-    with pytest.raises(TypeError):
-        class Child21(Base2, Base1):
-            pass
-
-
-def test_inheritance_several_rc():
-    class Base3(StaticProvider):
-        @static_provision_action
-        def _provide_one(self, mediator: Mediator, request: SampleRequest):
-            pass
-
-    with pytest.raises(TypeError):
-        class Child13(Base1, Base3):
-            pass
-
-    with pytest.raises(TypeError):
-        class Child1(Base1):
-            @static_provision_action
-            def _provide_one(self, mediator: Mediator, request: SampleRequest):
-                pass
diff --git a/tests/unit/retort/test_operating_retort.py b/tests/unit/retort/test_operating_retort.py
index 9b75295d..45b78c16 100644
--- a/tests/unit/retort/test_operating_retort.py
+++ b/tests/unit/retort/test_operating_retort.py
@@ -2,10 +2,9 @@
 from typing import List
 
 import pytest
-from tests_helpers import cond_list, raises_exc, with_cause, with_notes
+from tests_helpers import raises_exc, with_cause, with_notes
 
 from adaptix import AggregateCannotProvide, CannotProvide, ProviderNotFoundError, Retort
-from adaptix._internal.feature_requirement import HAS_STD_CLASSES_GENERICS
 from adaptix.conversion import get_converter
 
 
@@ -264,7 +263,7 @@ class BookDTO:
     ["list_tp", "list_tp_name"],
     [
         pytest.param(List, "List"),
-        *cond_list(HAS_STD_CLASSES_GENERICS, [pytest.param(list, "list")]),
+        pytest.param(list, "list"),
     ],
 )
 def test_cannot_produce_converter_no_coercer_complex_type(list_tp, list_tp_name):
diff --git a/tests/unit/type_tools/data_type_checking.py b/tests/unit/type_tools/data_type_checking.py
new file mode 100644
index 00000000..3f687831
--- /dev/null
+++ b/tests/unit/type_tools/data_type_checking.py
@@ -0,0 +1,16 @@
+import typing
+from collections.abc import Sequence
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    IntSeq = Sequence[int]
+
+
+if typing.TYPE_CHECKING:
+    StrSeq = Sequence[str]
+
+
+class Foo:
+    a: bool
+    b: "IntSeq"
+    c: "StrSeq"
diff --git a/tests/unit/type_tools/local_helpers.py b/tests/unit/type_tools/local_helpers.py
index 19d8bec6..9c3df9cf 100644
--- a/tests/unit/type_tools/local_helpers.py
+++ b/tests/unit/type_tools/local_helpers.py
@@ -1,7 +1,7 @@
 import operator
 import typing
 from functools import reduce
-from typing import List, Union
+from typing import Union
 
 from adaptix import TypeHint
 from adaptix._internal.type_tools import BaseNormType, NormParamSpecMarker, NormTV, make_norm_type, normalize_type
@@ -67,7 +67,7 @@ def assert_strict_equal(left: BaseNormType, right: BaseNormType):
     hash(right)
 
 
-def assert_normalize(tp: TypeHint, origin: TypeHint, args: List[typing.Hashable]):
+def assert_normalize(tp: TypeHint, origin: TypeHint, args: list[typing.Hashable]):
     assert_strict_equal(
         normalize_type(tp),
         make_norm_type(origin, tuple(args), source=tp),
diff --git a/tests/unit/type_tools/test_basic_utils.py b/tests/unit/type_tools/test_basic_utils.py
index 8573b5ea..e44c289c 100644
--- a/tests/unit/type_tools/test_basic_utils.py
+++ b/tests/unit/type_tools/test_basic_utils.py
@@ -1,7 +1,7 @@
 import collections
-import typing
 from collections import namedtuple
 from typing import (
+    Annotated,
     Any,
     Callable,
     Dict,
@@ -20,7 +20,7 @@
 import pytest
 from tests_helpers import cond_list, load_namespace
 
-from adaptix._internal.feature_requirement import HAS_ANNOTATED, HAS_PY_312, HAS_STD_CLASSES_GENERICS
+from adaptix._internal.feature_requirement import HAS_PY_312
 from adaptix._internal.type_tools import is_named_tuple_class, is_protocol, is_user_defined_generic
 from adaptix._internal.type_tools.basic_utils import (
     get_type_vars_of_parametrized,
@@ -213,9 +213,8 @@ def test_is_parametrized():
     assert is_parametrized(List[int])
     assert is_parametrized(List[T])
 
-    if HAS_STD_CLASSES_GENERICS:
-        assert is_parametrized(list[int])
-        assert is_parametrized(list[T])
+    assert is_parametrized(list[int])
+    assert is_parametrized(list[T])
 
     assert not is_parametrized(Union)
     assert is_parametrized(Union[int, str])
@@ -226,33 +225,23 @@ def test_is_parametrized():
 @pytest.mark.parametrize(
     ["tp", "result"],
     [
-        (list, bool(HAS_STD_CLASSES_GENERICS)),
+        (list, True),
         (List, True),
         (Dict, True),
         (List[T], True),
         (List[int], False),
-        *cond_list(
-            HAS_STD_CLASSES_GENERICS,
-            lambda: [
-                (list[T], True),
-                (list[int], False),
-            ],
-        ),
+        (list[T], True),
+        (list[int], False),
         *gen_ns_parametrize(
             lambda gen_ns: (gen_ns.Gen, True),
             lambda gen_ns: (gen_ns.Gen[T], True),
             lambda gen_ns: (gen_ns.Gen[int], False),
         ),
-        *cond_list(
-            HAS_ANNOTATED,
-            lambda: [
-                (typing.Annotated, False),
-                (typing.Annotated[int, "meta"], False),
-                (typing.Annotated[T, "meta"], True),
-                (typing.Annotated[list, "meta"], True),
-                (typing.Annotated[list[T], "meta"], True),
-            ],
-        ),
+        (Annotated, False),
+        (Annotated[int, "meta"], False),
+        (Annotated[T, "meta"], True),
+        (Annotated[list, "meta"], True),
+        (Annotated[list[T], "meta"], True),
         (type, False),  # cannot be parametrized
         (Type, True),
         *type_alias_ns_parametrize(
@@ -276,28 +265,18 @@ def test_is_generic(tp, result):
         (Dict, True),
         (List[T], False),
         (List[int], False),
-        *cond_list(
-            HAS_STD_CLASSES_GENERICS,
-            lambda: [
-                (list[T], False),
-                (list[int], False),
-            ],
-        ),
+        (list[T], False),
+        (list[int], False),
         *gen_ns_parametrize(
             lambda gen_ns: (gen_ns.Gen, True),
             lambda gen_ns: (gen_ns.Gen[T], False),
             lambda gen_ns: (gen_ns.Gen[int], False),
         ),
-        *cond_list(
-            HAS_ANNOTATED,
-            lambda: [
-                (typing.Annotated, False),
-                (typing.Annotated[int, "meta"], False),
-                (typing.Annotated[T, "meta"], False),
-                (typing.Annotated[list, "meta"], False),
-                (typing.Annotated[list[T], "meta"], False),
-            ],
-        ),
+        (Annotated, False),
+        (Annotated[int, "meta"], False),
+        (Annotated[T, "meta"], False),
+        (Annotated[list, "meta"], False),
+        (Annotated[list[T], "meta"], False),
         *type_alias_ns_parametrize(
             lambda type_alias_ns: (type_alias_ns.IntAlias, False),
             lambda type_alias_ns: (type_alias_ns.RecursiveAlias, False),
@@ -311,7 +290,7 @@ def test_is_bare_generic(tp, result):
     assert is_bare_generic(tp) == result
 
 
-def test_get_type_vars_of_parametrized(gen_ns):  # noqa: PLR0915
+def test_get_type_vars_of_parametrized(gen_ns):
     assert get_type_vars_of_parametrized(gen_ns.Gen[T]) == (T,)
     assert get_type_vars_of_parametrized(gen_ns.Gen[str]) == ()
     assert get_type_vars_of_parametrized(gen_ns.Gen) == ()
@@ -346,36 +325,33 @@ def test_get_type_vars_of_parametrized(gen_ns):  # noqa: PLR0915
     assert get_type_vars_of_parametrized(Callable[[T], T]) == (T,)
     assert get_type_vars_of_parametrized(Callable[[T, int, V], T]) == (T, V)
 
-    if HAS_STD_CLASSES_GENERICS:
-        assert get_type_vars_of_parametrized(list[T]) == (T,)
-        assert get_type_vars_of_parametrized(list[str]) == ()
-        assert get_type_vars_of_parametrized(dict[T, V]) == (T, V)
-        assert get_type_vars_of_parametrized(dict[str, V]) == (V,)
-        assert get_type_vars_of_parametrized(dict[T, str]) == (T,)
-        assert get_type_vars_of_parametrized(dict[str, str]) == ()
-        assert get_type_vars_of_parametrized(dict[V, T]) == (V, T)
+    assert get_type_vars_of_parametrized(list[T]) == (T,)
+    assert get_type_vars_of_parametrized(list[str]) == ()
+    assert get_type_vars_of_parametrized(dict[T, V]) == (T, V)
+    assert get_type_vars_of_parametrized(dict[str, V]) == (V,)
+    assert get_type_vars_of_parametrized(dict[T, str]) == (T,)
+    assert get_type_vars_of_parametrized(dict[str, str]) == ()
+    assert get_type_vars_of_parametrized(dict[V, T]) == (V, T)
 
-        assert get_type_vars_of_parametrized(tuple[()]) == ()
-        assert get_type_vars_of_parametrized(tuple[int]) == ()
-        assert get_type_vars_of_parametrized(tuple[int, T]) == (T,)
+    assert get_type_vars_of_parametrized(tuple[()]) == ()
+    assert get_type_vars_of_parametrized(tuple[int]) == ()
+    assert get_type_vars_of_parametrized(tuple[int, T]) == (T,)
 
     assert get_type_vars_of_parametrized(Generic) == ()
     assert get_type_vars_of_parametrized(Generic[T]) == (T,)
     assert get_type_vars_of_parametrized(Generic[T, V]) == (T, V)
 
-    if HAS_ANNOTATED:
-        assert get_type_vars_of_parametrized(typing.Annotated[int, "meta"]) == ()
-
-        assert get_type_vars_of_parametrized(typing.Annotated[list, "meta"]) == ()
-        assert get_type_vars_of_parametrized(typing.Annotated[list[int], "meta"]) == ()
-        assert get_type_vars_of_parametrized(typing.Annotated[list[T], "meta"]) == (T,)
+    assert get_type_vars_of_parametrized(Annotated[int, "meta"]) == ()
 
-        assert get_type_vars_of_parametrized(typing.Annotated[gen_ns.Gen, "meta"]) == ()
-        assert get_type_vars_of_parametrized(typing.Annotated[gen_ns.Gen[T], "meta"]) == (T,)
+    assert get_type_vars_of_parametrized(Annotated[list, "meta"]) == ()
+    assert get_type_vars_of_parametrized(Annotated[list[int], "meta"]) == ()
+    assert get_type_vars_of_parametrized(Annotated[list[T], "meta"]) == (T,)
 
-        assert get_type_vars_of_parametrized(typing.Annotated[Proto, "meta"]) == ()
-        assert get_type_vars_of_parametrized(typing.Annotated[Proto[T], "meta"]) == (T,)
+    assert get_type_vars_of_parametrized(Annotated[gen_ns.Gen, "meta"]) == ()
+    assert get_type_vars_of_parametrized(Annotated[gen_ns.Gen[T], "meta"]) == (T,)
 
+    assert get_type_vars_of_parametrized(Annotated[Proto, "meta"]) == ()
+    assert get_type_vars_of_parametrized(Annotated[Proto[T], "meta"]) == (T,)
 
 @pytest.mark.parametrize(
     ["cls", "result"],
@@ -439,17 +415,12 @@ class DictChildGeneric(dict):
     pass
 
 
-def std_classes_parametrized():
-    class ListAliasChild(list[int]):
-        pass
+class ListAliasChild(list[int]):
+    pass
 
-    class DictAliasChild(dict[str, str]):
-        pass
 
-    return [
-        (ListAliasChild, False),
-        (DictAliasChild, False),
-    ]
+class DictAliasChild(dict[str, str]):
+    pass
 
 
 @pytest.mark.parametrize(
@@ -457,7 +428,8 @@ class DictAliasChild(dict[str, str]):
     [
         (ListChildGeneric, False),
         (DictChildGeneric, False),
-        *cond_list(HAS_STD_CLASSES_GENERICS, std_classes_parametrized),
+        (ListAliasChild, False),
+        (DictAliasChild, False),
     ],
 )
 def test_is_generic_class_builtin_children(cls, result):
diff --git a/tests/unit/type_tools/test_normalize_type.py b/tests/unit/type_tools/test_normalize_type.py
index 049e3083..7e46d98d 100644
--- a/tests/unit/type_tools/test_normalize_type.py
+++ b/tests/unit/type_tools/test_normalize_type.py
@@ -5,7 +5,9 @@
 from dataclasses import InitVar
 from enum import Enum
 from itertools import permutations
+from types import GenericAlias
 from typing import (
+    Annotated,
     Any,
     Callable,
     ClassVar,
@@ -35,12 +37,10 @@
 from tests_helpers import cond_list, full_match, requires
 
 from adaptix._internal.feature_requirement import (
-    HAS_ANNOTATED,
     HAS_PARAM_SPEC,
     HAS_PY_310,
     HAS_PY_311,
     HAS_PY_312,
-    HAS_STD_CLASSES_GENERICS,
     HAS_TV_TUPLE,
     HAS_TYPE_ALIAS,
     HAS_TYPE_GUARD,
@@ -108,11 +108,10 @@ def test_generic_concrete_one_arg(tp, alias):
         alias,
         tp, [nt_zero(Any)],
     )
-    if HAS_STD_CLASSES_GENERICS:
-        assert_normalize(
-            tp[int],
-            tp, [nt_zero(int)],
-        )
+    assert_normalize(
+        tp[int],
+        tp, [nt_zero(int)],
+    )
     assert_normalize(
         alias[int],
         tp, [nt_zero(int)],
@@ -137,11 +136,10 @@ def test_generic_concrete_two_args(tp, alias):
         alias,
         tp, [nt_zero(Any), nt_zero(Any)],
     )
-    if HAS_STD_CLASSES_GENERICS:
-        assert_normalize(
-            tp[int, str],
-            tp, [nt_zero(int), nt_zero(str)],
-        )
+    assert_normalize(
+        tp[int, str],
+        tp, [nt_zero(int), nt_zero(str)],
+    )
     assert_normalize(
         alias[int, str],
         tp, [nt_zero(int), nt_zero(str)],
@@ -157,27 +155,24 @@ def test_special_generics():
         Tuple,
         tuple, [nt_zero(Any), ...],
     )
-    if HAS_STD_CLASSES_GENERICS:
-        assert_normalize(
-            tuple[int],
-            tuple, [nt_zero(int)],
-        )
+    assert_normalize(
+        tuple[int],
+        tuple, [nt_zero(int)],
+    )
     assert_normalize(
         Tuple[int],
         tuple, [nt_zero(int)],
     )
-    if HAS_STD_CLASSES_GENERICS:
-        assert_normalize(
-            tuple[int, ...],
-            tuple, [nt_zero(int), ...],
-        )
+    assert_normalize(
+        tuple[int, ...],
+        tuple, [nt_zero(int), ...],
+    )
     assert_normalize(
         Tuple[int, ...],
         tuple, [nt_zero(int), ...],
     )
 
-    if HAS_STD_CLASSES_GENERICS:
-        assert_normalize(tuple[()], tuple, [])
+    assert_normalize(tuple[()], tuple, [])
     assert_normalize(Tuple[()], tuple, [])
 
     any_str_placeholder = make_norm_type(
@@ -195,7 +190,7 @@ def test_special_generics():
     "callable_tp",
     [
         Callable,
-        *cond_list(HAS_STD_CLASSES_GENERICS, [c_abc.Callable]),
+        c_abc.Callable,
     ],
 )
 def test_callable(callable_tp):
@@ -275,17 +270,12 @@ def test_type(make_union):
 
     assert_normalize(
         Type[make_union[int, str]],
-        Union, [normalize_type(Type[int]), normalize_type(Type[str])],
-    )
-
-    assert_normalize(
-        Union[Type[make_union[int, str]], Type[bool]],
-        Union, [normalize_type(Type[int]), normalize_type(Type[str]), normalize_type(Type[bool])],
+        Union, [normalize_type(type[int]), normalize_type(type[str])],
     )
 
     assert_normalize(
-        Union[Type[make_union[int, str]], Type[int]],
-        Union, [normalize_type(Type[int]), normalize_type(Type[str])],
+        Union[type[make_union[int, str]], type[int]],
+        Union, [normalize_type(type[int]), normalize_type(type[str])],
     )
 
 
@@ -408,10 +398,7 @@ def test_final():
     )
 
 
-@requires(HAS_ANNOTATED)
 def test_annotated():
-    from typing import Annotated
-
     pytest.raises(NotSubscribedError, lambda: normalize_type(Annotated))
 
     assert_normalize(
@@ -458,10 +445,10 @@ def test_union(make_union):
     )
 
     assert_normalize(
-        make_union[Type[list], Type[Union[List, str]]],
+        make_union[type[list], type[Union[List, str]]],
         Union, [
-            normalize_type(Union[Type[list], Type[List]]),
-            normalize_type(Type[str]),
+            normalize_type(Union[type[list], type[List]]),
+            normalize_type(type[str]),
         ],
     )
 
@@ -914,10 +901,7 @@ def test_type_alias():
     )
 
 
-@requires(HAS_STD_CLASSES_GENERICS)
 def test_types_generic_alias():
-    from types import GenericAlias
-
     assert_normalize(
         GenericAlias(list, (int,)),
         list, [nt_zero(int)],
@@ -1017,7 +1001,7 @@ class Array(Generic[Unpack[ShapeT]]):
 
     assert_normalize(
         Array,
-        Array, [normalize_type(Unpack[Tuple[Any, ...]])],
+        Array, [normalize_type(Unpack[tuple[Any, ...]])],
     )
     assert_normalize(
         Array[int],
@@ -1051,7 +1035,7 @@ class PreArray(Generic[DType, Unpack[ShapeT]]):
 
     assert_normalize(
         PreArray,
-        PreArray, [nt_zero(Any), normalize_type(Unpack[Tuple[Any, ...]])],
+        PreArray, [nt_zero(Any), normalize_type(Unpack[tuple[Any, ...]])],
     )
     assert_normalize(
         PreArray[int],
@@ -1088,7 +1072,7 @@ class PostArray(Generic[Unpack[ShapeT], DType]):
 
     assert_normalize(
         PostArray,
-        PostArray, [normalize_type(Unpack[Tuple[Any, ...]]), nt_zero(Any)],
+        PostArray, [normalize_type(Unpack[tuple[Any, ...]]), nt_zero(Any)],
     )
     assert_normalize(
         PostArray[int],
diff --git a/tests/unit/type_tools/test_type_evaler.py b/tests/unit/type_tools/test_type_evaler.py
new file mode 100644
index 00000000..11f563a2
--- /dev/null
+++ b/tests/unit/type_tools/test_type_evaler.py
@@ -0,0 +1,18 @@
+from collections.abc import Sequence
+from pathlib import Path
+
+from tests_helpers.misc import import_local_module, temp_module
+
+from adaptix._internal.type_tools import get_all_type_hints
+from adaptix.type_tools import exec_type_checking
+
+
+def test_exec_type_checking():
+    module = import_local_module(Path(__file__).with_name("data_type_checking.py"))
+    with temp_module(module):
+        exec_type_checking(module)
+        assert get_all_type_hints(module.Foo) == {
+            "a": bool,
+            "b": Sequence[int],
+            "c": Sequence[str],
+        }