From a47e1cff36bc70159d8168c9a03214f4ea1f3253 Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
Date: Wed, 20 Aug 2025 03:46:39 +0800
Subject: [PATCH] Fixes for pydantic

---
 viu_media/cli/cli.py                          |  2 +-
 .../commands/anilist/commands/downloads.py    |  2 +-
 .../cli/commands/anilist/commands/search.py   |  2 +-
 viu_media/cli/commands/config.py              |  2 +-
 .../cli/commands/registry/commands/search.py  |  2 +-
 viu_media/cli/config/editor.py                |  6 +-
 viu_media/cli/config/generate.py              |  9 +--
 viu_media/cli/config/loader.py                |  2 +-
 .../cli/interactive/menu/media/episodes.py    |  2 +-
 .../interactive/menu/media/play_downloads.py  |  8 +--
 .../interactive/menu/media/player_controls.py |  6 +-
 .../cli/interactive/menu/media/servers.py     |  2 +-
 viu_media/cli/interactive/state.py            |  3 +-
 viu_media/cli/options.py                      | 55 ++++++-------------
 viu_media/cli/service/auth/service.py         |  4 +-
 viu_media/cli/service/registry/models.py      |  6 +-
 viu_media/cli/service/registry/service.py     |  8 +--
 viu_media/cli/service/session/model.py        |  3 +-
 viu_media/cli/service/session/service.py      |  4 +-
 viu_media/core/downloader/model.py            |  3 +-
 viu_media/libs/media_api/types.py             |  5 +-
 viu_media/libs/provider/anime/types.py        |  3 +-
 22 files changed, 57 insertions(+), 82 deletions(-)

diff --git a/viu_media/cli/cli.py b/viu_media/cli/cli.py
index 7a6c36a..bf0f4a2 100644
--- a/viu_media/cli/cli.py
+++ b/viu_media/cli/cli.py
@@ -103,7 +103,7 @@ def cli(ctx: click.Context, **options: "Unpack[Options]"):
 
     loader = ConfigLoader(config_path=USER_CONFIG)
     config = (
-        AppConfig.model_validate(cli_overrides)
+        AppConfig(**cli_overrides)
         if options["no_config"]
         else loader.load(cli_overrides)
     )
diff --git a/viu_media/cli/commands/anilist/commands/downloads.py b/viu_media/cli/commands/anilist/commands/downloads.py
index 18be58e..70ebdef 100644
--- a/viu_media/cli/commands/anilist/commands/downloads.py
+++ b/viu_media/cli/commands/anilist/commands/downloads.py
@@ -107,7 +107,7 @@ def downloads(
         return
 
     if output_json:
-        print(json.dumps(result.model_dump(mode="json"), indent=2))
+        print(result.json(indent=2))
         return
 
     from ....interactive.session import session
diff --git a/viu_media/cli/commands/anilist/commands/search.py b/viu_media/cli/commands/anilist/commands/search.py
index d4ae366..88295df 100644
--- a/viu_media/cli/commands/anilist/commands/search.py
+++ b/viu_media/cli/commands/anilist/commands/search.py
@@ -297,7 +297,7 @@ def search(config: AppConfig, **options: "Unpack[SearchOptions]"):
 
         if dump_json:
             # Use Pydantic's built-in serialization
-            print(json.dumps(search_result.model_dump(mode="json")))
+            print(search_result.json())
         else:
             # Launch interactive session for browsing results
             from ....interactive.session import session
diff --git a/viu_media/cli/commands/config.py b/viu_media/cli/commands/config.py
index 8be3a30..f58c4c3 100644
--- a/viu_media/cli/commands/config.py
+++ b/viu_media/cli/commands/config.py
@@ -93,7 +93,7 @@ def config(
     elif view_json:
         import json
 
-        print(json.dumps(user_config.model_dump(mode="json")))
+        print(user_config.json())
     elif generate_desktop_entry:
         _generate_desktop_entry()
     elif interactive:
diff --git a/viu_media/cli/commands/registry/commands/search.py b/viu_media/cli/commands/registry/commands/search.py
index 0ebb696..133e908 100644
--- a/viu_media/cli/commands/registry/commands/search.py
+++ b/viu_media/cli/commands/registry/commands/search.py
@@ -110,7 +110,7 @@ def search(
             return
 
         if output_json:
-            print(json.dumps(result.model_dump(mode="json"), indent=2))
+            print(result.json(indent=2))
             return
 
         _display_search_results(console, result, config.general.icons)
diff --git a/viu_media/cli/config/editor.py b/viu_media/cli/config/editor.py
index cf48b11..cc073b5 100644
--- a/viu_media/cli/config/editor.py
+++ b/viu_media/cli/config/editor.py
@@ -15,7 +15,7 @@ class InteractiveConfigEditor:
     """A wizard to guide users through setting up their configuration interactively."""
 
     def __init__(self, current_config: AppConfig):
-        self.config = current_config.model_copy(deep=True)  # Work on a copy
+        self.config = current_config.copy(deep=True)  # Work on a copy
 
     def run(self) -> AppConfig:
         """Starts the interactive configuration wizard."""
@@ -48,7 +48,7 @@ class InteractiveConfigEditor:
         """Generates prompts for all fields in a given config section."""
         print(f"\n--- [bold magenta]{section_name.title()} Settings[/bold magenta] ---")
 
-        for field_name, field_info in section_model.model_fields.items():
+        for field_name, field_info in section_model.__fields__.items():
             # Skip complex multi-line fields as agreed
             if section_name == "fzf" and field_name in ["opts", "header_ascii_art"]:
                 continue
@@ -77,7 +77,7 @@ class InteractiveConfigEditor:
         """Creates the appropriate InquirerPy prompt for a given Pydantic field."""
         field_type = field_info.annotation
         help_text = textwrap.fill(
-            field_info.description or "No description available.", width=80
+            field_info.field_info.description or "No description available.", width=80
         )
         message = f"{field_name.replace('_', ' ').title()}:"
 
diff --git a/viu_media/cli/config/generate.py b/viu_media/cli/config/generate.py
index d0a3546..b57eb44 100644
--- a/viu_media/cli/config/generate.py
+++ b/viu_media/cli/config/generate.py
@@ -6,8 +6,7 @@
 from pathlib import Path
 from typing import Any, Literal, get_args, get_origin
 
-from pydantic.fields import ComputedFieldInfo, FieldInfo
-from pydantic_core import PydanticUndefined
+from pydantic.fields import FieldInfo
 
 from ...core.config import AppConfig
 from ...core.constants import APP_ASCII_ART, CLI_NAME, DISCORD_INVITE, REPO_HOME
@@ -49,17 +48,13 @@
     config_content_parts = [CONFIG_HEADER]
 
     for section_name, section_model in app_model:
-        section_title = section_model.model_config.get("title", section_name.title())
+        section_title = getattr(section_model.__config__, "title", "")
 
         config_content_parts.append(f"\n#\n# {section_title}\n#")
         config_content_parts.append(f"[{section_name}]")
 
-        for field_name, field_info in itertools.chain(
-            section_model.model_fields.items(),
-            section_model.model_computed_fields.items(),
-        ):
-            # --- Generate Comments ---
-            description = field_info.description or ""
+        for field_name, field_info in section_model.__fields__.items():
+            description = field_info.field_info.description or ""
             if description:
                 wrapped_comment = textwrap.fill(
                     description,
@@ -81,7 +76,7 @@
 
             if (
                 hasattr(field_info, "default")
-                and field_info.default is not PydanticUndefined
+                and field_info.default != None
             ):
                 default_val = (
                     field_info.default.value
@@ -138,13 +133,9 @@
     return f'"{str(value)}"'
 
 
-def _get_field_type_comment(field_info: FieldInfo | ComputedFieldInfo) -> str:
+def _get_field_type_comment(field_info: FieldInfo) -> str:
     """Generate a comment with type information for a field."""
-    field_type = (
-        field_info.annotation
-        if isinstance(field_info, FieldInfo)
-        else field_info.return_type
-    )
+    field_type = field_info.type_
 
     possible_values = []
     if field_type is not None:
@@ -184,15 +175,15 @@
     return ""
 
 
-def _get_range_info(field_info: FieldInfo | ComputedFieldInfo) -> str:
+def _get_range_info(field_info: FieldInfo) -> str:
     """Get a string describing the numeric range of a field."""
     constraints = {}
     if (
         isinstance(field_info, FieldInfo)
-        and hasattr(field_info, "metadata")
-        and field_info.metadata
+        and hasattr(field_info.field_info, "metadata")
+        and field_info.field_info.metadata
     ):
-        for constraint in field_info.metadata:
+        for constraint in field_info.field_info.metadata:
             constraint_type = type(constraint).__name__
             if constraint_type == "Ge" and hasattr(constraint, "ge"):
                 constraints["min"] = constraint.ge
diff --git a/viu_media/cli/config/loader.py b/viu_media/cli/config/loader.py
index 81b5bd6..8c9ab5d 100644
--- a/viu_media/cli/config/loader.py
+++ b/viu_media/cli/config/loader.py
@@ -104,7 +104,7 @@ class ConfigLoader:
                     config_dict[section] = values
 
         try:
-            app_config = AppConfig.model_validate(config_dict)
+            app_config = AppConfig(**config_dict)
             return app_config
         except ValidationError as e:
             error_message = (
diff --git a/viu_media/cli/interactive/menu/media/episodes.py b/viu_media/cli/interactive/menu/media/episodes.py
index bbde16d..ec75961 100644
--- a/viu_media/cli/interactive/menu/media/episodes.py
+++ b/viu_media/cli/interactive/menu/media/episodes.py
@@ -71,7 +71,7 @@ def episodes(ctx: Context, state: State) -> State | InternalDirective:
     return State(
         menu_name=MenuName.SERVERS,
         media_api=state.media_api,
-        provider=state.provider.model_copy(
+        provider=state.provider.copy(
             update={"episode_": chosen_episode, "start_time_": start_time}
         ),
     )
diff --git a/viu_media/cli/interactive/menu/media/play_downloads.py b/viu_media/cli/interactive/menu/media/play_downloads.py
index 924efe9..5c04a48 100644
--- a/viu_media/cli/interactive/menu/media/play_downloads.py
+++ b/viu_media/cli/interactive/menu/media/play_downloads.py
@@ -78,7 +78,7 @@ def play_downloads(ctx: Context, state: State) -> State | InternalDirective:
     return State(
         menu_name=MenuName.DOWNLOADS_PLAYER_CONTROLS,
         media_api=state.media_api,
-        provider=state.provider.model_copy(
+        provider=state.provider.copy(
             update={"episode_": chosen_episode, "start_time_": start_time}
         ),
     )
@@ -149,7 +149,7 @@ def downloads_player_controls(
         return State(
             menu_name=MenuName.DOWNLOADS_PLAYER_CONTROLS,
             media_api=state.media_api,
-            provider=state.provider.model_copy(
+            provider=state.provider.copy(
                 update={"episode_": next_episode_num, "start_time_": None}
             ),
         )
@@ -224,7 +224,7 @@ def _next_episode(ctx: Context, state: State) -> MenuAction:
             return State(
                 menu_name=MenuName.DOWNLOADS_PLAYER_CONTROLS,
                 media_api=state.media_api,
-                provider=state.provider.model_copy(
+                provider=state.provider.copy(
                     update={"episode_": next_episode_num, "start_time_": None}
                 ),
             )
@@ -273,7 +273,7 @@ def _previous_episode(ctx: Context, state: State) -> MenuAction:
             return State(
                 menu_name=MenuName.DOWNLOADS_PLAYER_CONTROLS,
                 media_api=state.media_api,
-                provider=state.provider.model_copy(
+                provider=state.provider.copy(
                     update={"episode_": prev_episode_num, "start_time_": None}
                 ),
             )
diff --git a/viu_media/cli/interactive/menu/media/player_controls.py b/viu_media/cli/interactive/menu/media/player_controls.py
index 19811ac..3618b9d 100644
--- a/viu_media/cli/interactive/menu/media/player_controls.py
+++ b/viu_media/cli/interactive/menu/media/player_controls.py
@@ -42,7 +42,7 @@ def player_controls(ctx: Context, state: State) -> Union[State, InternalDirectiv
         return State(
             menu_name=MenuName.SERVERS,
             media_api=state.media_api,
-            provider=state.provider.model_copy(update={"episode_": next_episode_num}),
+            provider=state.provider.copy(update={"episode_": next_episode_num}),
         )
 
     # --- Menu Options ---
@@ -115,7 +115,7 @@ def _next_episode(ctx: Context, state: State) -> MenuAction:
             return State(
                 menu_name=MenuName.SERVERS,
                 media_api=state.media_api,
-                provider=state.provider.model_copy(
+                provider=state.provider.copy(
                     update={"episode_": next_episode_num}
                 ),
             )
@@ -149,7 +149,7 @@ def _previous_episode(ctx: Context, state: State) -> MenuAction:
             return State(
                 menu_name=MenuName.SERVERS,
                 media_api=state.media_api,
-                provider=state.provider.model_copy(
+                provider=state.provider.copy(
                     update={"episode_": prev_episode_num}
                 ),
             )
diff --git a/viu_media/cli/interactive/menu/media/servers.py b/viu_media/cli/interactive/menu/media/servers.py
index 7b99137..9ee8642 100644
--- a/viu_media/cli/interactive/menu/media/servers.py
+++ b/viu_media/cli/interactive/menu/media/servers.py
@@ -102,7 +102,7 @@ def servers(ctx: Context, state: State) -> State | InternalDirective:
     return State(
         menu_name=MenuName.PLAYER_CONTROLS,
         media_api=state.media_api,
-        provider=state.provider.model_copy(
+        provider=state.provider.copy(
             update={
                 "servers_": server_map,
                 "server_name_": selected_server.name,
diff --git a/viu_media/cli/interactive/state.py b/viu_media/cli/interactive/state.py
index 5a2df0e..181c975 100644
--- a/viu_media/cli/interactive/state.py
+++ b/viu_media/cli/interactive/state.py
@@ -51,7 +51,8 @@ class InternalDirective(Enum):
 
 
 class StateModel(BaseModel):
-    model_config = ConfigDict(frozen=True)
+    class Config:
+        frozen=True
 
 
 class MediaApiState(StateModel):
diff --git a/viu_media/cli/options.py b/viu_media/cli/options.py
index e0ccf17..e121e90 100644
--- a/viu_media/cli/options.py
+++ b/viu_media/cli/options.py
@@ -6,7 +6,6 @@
 import click
 from pydantic import BaseModel
 from pydantic.fields import FieldInfo
-from pydantic_core import PydanticUndefined
 
 from ..core.config.model import OtherConfig
 
@@ -53,11 +51,11 @@ def options_from_model(model: type[BaseModel], parent_name: str = "") -> Callabl
     model_name = model.__name__.lower().replace("config", "")
 
     # Introspect the model's fields
-    for field_name, field_info in model.model_fields.items():
+    for field_name, field_info in model.__fields__.items():
         if isinstance(field_info.annotation, type) and issubclass(
-            field_info.annotation, BaseModel
+            field_info.type_, BaseModel
         ):
-            nested_decorators = options_from_model(field_info.annotation, field_name)
+            nested_decorators = options_from_model(field_info.type_, field_name)
             nested_decorator_list = getattr(nested_decorators, "decorators", [])
             decorators.extend(nested_decorator_list)
             continue
@@ -68,17 +66,17 @@ def options_from_model(model: type[BaseModel], parent_name: str = "") -> Callabl
             cli_name = f"--{field_name.replace('_', '-')}"
         kwargs = {
             "type": _get_click_type(field_info),
-            "help": field_info.description or "",
+            "help": field_info.field_info.description or "",
         }
 
         if (
-            field_info.annotation is not None
-            and isinstance(field_info.annotation, type)
-            and issubclass(field_info.annotation, Enum)
+            field_info.type_ is not None
+            and isinstance(field_info.type_, type)
+            and issubclass(field_info.type_, Enum)
         ):
-            kwargs["default"] = field_info.default.value
-        elif field_info.annotation is bool:
-            if field_info.default is not PydanticUndefined:
+            kwargs["default"] = field_info.default.value if field_info.default is not None else None
+        elif field_info.type_ is bool:
+            if field_info.default is not None:
                 kwargs["default"] = field_info.default
                 kwargs["show_default"] = True
             if is_external_tool:
@@ -87,7 +85,7 @@ def options_from_model(model: type[BaseModel], parent_name: str = "") -> Callabl
                 )
             else:
                 cli_name = f"{cli_name}/--no-{field_name.replace('_', '-')}"
-        elif field_info.default is not PydanticUndefined:
+        elif field_info.default is not None:
             kwargs["default"] = field_info.default
             kwargs["show_default"] = True
 
@@ -101,27 +99,6 @@ def options_from_model(model: type[BaseModel], parent_name: str = "") -> Callabl
             )
         )
 
-    for field_name, computed_field_info in model.model_computed_fields.items():
-        if is_external_tool:
-            cli_name = f"--{model_name}-{field_name.replace('_', '-')}"
-        else:
-            cli_name = f"--{field_name.replace('_', '-')}"
-
-        kwargs = {
-            "type": TYPE_MAP[computed_field_info.return_type],
-            "help": computed_field_info.description or "",
-        }
-
-        decorators.append(
-            click.option(
-                cli_name,
-                cls=ConfigOption,
-                model_name=model_name,
-                field_name=field_name,
-                **kwargs,
-            )
-        )
-
     def decorator(f: Callable) -> Callable:
         # Apply the decorators in reverse order to the function
         for deco in reversed(decorators):
@@ -135,7 +112,7 @@ def options_from_model(model: type[BaseModel], parent_name: str = "") -> Callabl
 
 def _get_click_type(field_info: FieldInfo) -> Any:
     """Maps a Pydantic field's type to a corresponding click type."""
-    field_type = field_info.annotation
+    field_type = field_info.type_
 
     # check if type is enum
     if (
@@ -158,16 +135,16 @@ def _get_click_type(field_info: FieldInfo) -> Any:
             return click.Choice(args)
 
     # Check for examples in field_info - use as choices
-    if hasattr(field_info, "examples") and field_info.examples:
-        return click.Choice(field_info.examples)
+    if hasattr(field_info.field_info, "examples") and field_info.field_info.examples:
+        return click.Choice(field_info.field_info.examples)
 
     # Check for numeric constraints and create click.Range
     if field_type in (int, float):
         constraints = {}
 
         # Extract constraints from field_info.metadata
-        if hasattr(field_info, "metadata") and field_info.metadata:
-            for constraint in field_info.metadata:
+        if hasattr(field_info.field_info, "metadata") and field_info.field_info.metadata:
+            for constraint in field_info.field_info.metadata:
                 constraint_type = type(constraint).__name__
 
                 if constraint_type == "Ge" and hasattr(constraint, "ge"):
diff --git a/viu_media/cli/service/auth/service.py b/viu_media/cli/service/auth/service.py
index 67b9fb4..aed6fb0 100644
--- a/viu_media/cli/service/auth/service.py
+++ b/viu_media/cli/service/auth/service.py
@@ -43,11 +43,11 @@ class AuthService:
 
         with self.path.open("r", encoding="utf-8") as f:
             data = json.load(f)
-            self._auth = AuthModel.model_validate(data)
+            self._auth = AuthModel(**data)
             return self._auth
 
     def _save_auth(self, auth: AuthModel):
         with self._lock:
             with AtomicWriter(self.path) as f:
-                json.dump(auth.model_dump(), f, indent=2)
+                f.write(auth.json(indent=2))
             logger.info(f"Successfully saved user credentials to {self.path}")
diff --git a/viu_media/cli/service/registry/models.py b/viu_media/cli/service/registry/models.py
index 4420962..af694c4 100644
--- a/viu_media/cli/service/registry/models.py
+++ b/viu_media/cli/service/registry/models.py
@@ -4,7 +4,7 @@ from enum import Enum
 from pathlib import Path
 from typing import Dict, Literal, Optional
 
-from pydantic import BaseModel, Field, computed_field
+from pydantic import BaseModel, Field
 
 from ....core.utils import converter
 from ....libs.media_api.types import MediaItem, UserMediaListStatus
@@ -67,7 +67,6 @@ class MediaRegistryIndexEntry(BaseModel):
     start_date: datetime = Field(default_factory=datetime.now)
     completed_at: datetime = Field(default_factory=datetime.now)
 
-    @computed_field
     @property
     def watch_completion_percentage(self) -> float:
         """Watch completion percentage."""
@@ -85,7 +84,6 @@ class MediaRegistryIndex(BaseModel):
 
     media_index: Dict[str, MediaRegistryIndexEntry] = Field(default_factory=dict)
 
-    @computed_field
     @property
     def status_breakdown(self) -> Dict[str, int]:
         """Get breakdown by user status."""
@@ -94,7 +92,6 @@ class MediaRegistryIndex(BaseModel):
             breakdown[entry.status.value] = breakdown.get(entry.status.value, 0) + 1
         return breakdown
 
-    @computed_field
     @property
     def media_count_breakdown(self) -> Dict[str, int]:
         breakdown = {}
@@ -102,7 +99,6 @@ class MediaRegistryIndex(BaseModel):
             breakdown[entry.media_api] = breakdown.get(entry.media_api, 0) + 1
         return breakdown
 
-    @computed_field
     @property
     def media_count(self) -> int:
         """Get the number of media."""
diff --git a/viu_media/cli/service/registry/service.py b/viu_media/cli/service/registry/service.py
index 7f0a411..45c2ba1 100644
--- a/viu_media/cli/service/registry/service.py
+++ b/viu_media/cli/service/registry/service.py
@@ -62,7 +62,7 @@ class MediaRegistryService:
         if self._index_file.exists():
             with self._index_file.open("r", encoding="utf-8") as f:
                 data = json.load(f)
-            self._index = MediaRegistryIndex.model_validate(data)
+            self._index = MediaRegistryIndex(**data)
         else:
             self._index = MediaRegistryIndex()
             self._save_index(self._index)
@@ -81,7 +81,7 @@ class MediaRegistryService:
         with self._lock:
             index.last_updated = datetime.now()
             with AtomicWriter(self._index_file) as f:
-                json.dump(index.model_dump(mode="json"), f, indent=2)
+                f.write(index.json(indent=2))
 
             logger.debug("saved registry index")
 
@@ -107,7 +107,7 @@ class MediaRegistryService:
 
         data = json.load(record_file.open(mode="r", encoding="utf-8"))
 
-        record = MediaRecord.model_validate(data)
+        record = MediaRecord(**data)
 
         # logger.debug(f"Loaded media record for {media_id}")
         return record
@@ -141,7 +141,7 @@ class MediaRegistryService:
             record_file = self._get_media_file_path(media_id)
 
             with AtomicWriter(record_file) as f:
-                json.dump(record.model_dump(mode="json"), f, indent=2, default=str)
+                f.write(record.json(indent=2))
 
             logger.debug(f"Saved media record for {media_id}")
             return True
diff --git a/viu_media/cli/service/session/model.py b/viu_media/cli/service/session/model.py
index 1aa8ddb..085d3b9 100644
--- a/viu_media/cli/service/session/model.py
+++ b/viu_media/cli/service/session/model.py
@@ -1,7 +1,7 @@
 from datetime import datetime
 from typing import List, Optional
 
-from pydantic import BaseModel, Field, computed_field
+from pydantic import BaseModel, Field
 
 from ...interactive.state import State
 
@@ -16,7 +16,6 @@ class Session(BaseModel):
     description: Optional[str] = None
     is_from_crash: bool = False
 
-    @computed_field
     @property
     def state_count(self) -> int:
         return len(self.history)
diff --git a/viu_media/cli/service/session/service.py b/viu_media/cli/service/session/service.py
index 3cdb625..e930a6f 100644
--- a/viu_media/cli/service/session/service.py
+++ b/viu_media/cli/service/session/service.py
@@ -65,7 +65,7 @@ class SessionsService:
     def _save_session(self, session: Session):
         path = self.dir / f"{session.name}.json"
         with AtomicWriter(path) as f:
-            json.dump(session.model_dump(mode="json", by_alias=True), f)
+            f.write(session.json(by_alias=True))
 
     def _load_session(self, session_name: str) -> Optional[Session]:
         path = self.dir / f"{session_name}.json"
@@ -75,7 +75,7 @@ class SessionsService:
 
         with path.open("r", encoding="utf-8") as f:
             data = json.load(f)
-            session = Session.model_validate(data)
+            session = Session(**data)
 
         logger.info(f"Session loaded from {path} with {session.state_count} states")
         return session
diff --git a/viu_media/core/downloader/model.py b/viu_media/core/downloader/model.py
index 3c7a07b..a7204b8 100644
--- a/viu_media/core/downloader/model.py
+++ b/viu_media/core/downloader/model.py
@@ -26,4 +26,5 @@ class DownloadResult(BaseModel):
     anime_title: str = Field(description="Title of the anime")
     episode_title: str = Field(description="Title of the episode")
 
-    model_config = {"arbitrary_types_allowed": True}
+    class Config:
+        arbitrary_types_allowed=True
diff --git a/viu_media/libs/media_api/types.py b/viu_media/libs/media_api/types.py
index 7118f35..630c2b7 100644
--- a/viu_media/libs/media_api/types.py
+++ b/viu_media/libs/media_api/types.py
@@ -76,7 +76,8 @@
 
 # MODELS
 class BaseMediaApiModel(BaseModel):
-    model_config = ConfigDict(frozen=True)
+    class Config:
+        frozen=True
 
 
 class MediaImage(BaseMediaApiModel):
@@ -408,4 +409,7 @@
     _2024 = "2024"
     _2025 = "2025"
     _2026 = "2026"
-    
\ No newline at end of file
+
+AiringScheduleResult.update_forward_refs()
+CharacterSearchResult.update_forward_refs()
+MediaTagItem.update_forward_refs()

diff --git a/viu_media/libs/provider/anime/types.py b/viu_media/libs/provider/anime/types.py
index c8afb96..e1a918c 100644
--- a/viu_media/libs/provider/anime/types.py
+++ b/viu_media/libs/provider/anime/types.py
@@ -37,7 +37,8 @@ class MediaTranslationType(Enum):
 
 # MODELS
 class BaseAnimeProviderModel(BaseModel):
-    model_config = ConfigDict(frozen=True)
+    class Config:
+        frozen=True
 
 
 class PageInfo(BaseAnimeProviderModel):

--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,7 @@
   "httpx>=0.28.1",
   "inquirerpy>=0.3.4",
   "pycryptodomex>=3.23.0",
-  "pydantic>=2.11.7",
+  "pydantic",
   "rich>=13.9.2",
 ]
 
