Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions changes/12451.misc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enable mypy's `explicit-override` error code and add the missing `@typing.override` decorators across the codebase, so every method overriding a base-class method is explicitly marked. `ai.backend.client.cli.pretty` is exempted per-module because mypy cannot recognise `@override` on a property overriding a base instance attribute.
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ namespace_packages = true
explicit_package_bases = true
python_executable = "dist/export/python/virtualenvs/python-default/3.13.7/bin/python"
disable_error_code = ["typeddict-unknown-key", "import-untyped"]
enable_error_code = ["possibly-undefined"]
enable_error_code = ["possibly-undefined", "explicit-override"]
warn_unreachable = true

# Disable errors in client CLI, web server, and tests
Expand Down Expand Up @@ -377,6 +377,10 @@ module = [
]
ignore_errors = true

[[tool.mypy.overrides]]
module = "ai.backend.client.cli.pretty"
disable_error_code = ["explicit-override"]

[tool.pyright]
pythonVersion = "3.13"
extraPaths = ["src"]
Expand Down
4 changes: 3 additions & 1 deletion src/ai/backend/account_manager/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from dataclasses import dataclass
from pathlib import Path
from pprint import pformat
from typing import Annotated, Any
from typing import Annotated, Any, override

import click
from pydantic import (
Expand Down Expand Up @@ -59,9 +59,11 @@ class HostPortPair(BaseSchema):
host: Annotated[str, Field(examples=["127.0.0.1"])]
port: Annotated[int, Field(gt=0, lt=65536, examples=[8201])]

@override
def __repr__(self) -> str:
return f"{self.host}:{self.port}"

@override
def __str__(self) -> str:
return self.__repr__()

Expand Down
5 changes: 4 additions & 1 deletion src/ai/backend/account_manager/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from __future__ import annotations

import json
from typing import Any
from typing import Any, override

from aiohttp import web

Expand Down Expand Up @@ -51,6 +51,7 @@ def __init__(
self.body_dict = body
self.body = json.dumps(body).encode()

@override
def __str__(self) -> str:
lines = []
if self.extra_msg:
Expand All @@ -61,6 +62,7 @@ def __str__(self) -> str:
lines.append(" -> extra_data: " + repr(self.extra_data))
return "\n".join(lines)

@override
def __repr__(self) -> str:
lines = []
if self.extra_msg:
Expand All @@ -73,6 +75,7 @@ def __repr__(self) -> str:
lines.append(" -> extra_data: " + repr(self.extra_data))
return "\n".join(lines)

@override
def __reduce__(self) -> tuple[type[BackendError], tuple[Any, ...], dict[str, Any]]:
return (
type(self),
Expand Down
9 changes: 9 additions & 0 deletions src/ai/backend/account_manager/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Self,
TypeVar,
cast,
override,
)

import sqlalchemy as sa
Expand Down Expand Up @@ -55,11 +56,13 @@ class GUID[UUID_SubType: uuid.UUID](TypeDecorator[uuid.UUID]):
uuid_subtype_func: ClassVar[Callable[[Any], Any]] = lambda v: v
cache_ok = True

@override
def load_dialect_impl(self, dialect: Dialect) -> TypeDecorator[Any]:
if dialect.name == "postgresql":
return cast(TypeDecorator[Any], dialect.type_descriptor(UUID()))
return cast(TypeDecorator[Any], dialect.type_descriptor(CHAR(16)))

@override
def process_bind_param(
self, value: UUID_SubType | uuid.UUID | None, dialect: Dialect
) -> str | bytes | None:
Expand All @@ -73,6 +76,7 @@ def process_bind_param(
return str(value)
return value.bytes

@override
def process_result_value(self, value: Any, dialect: Dialect) -> UUID_SubType | None:
if value is None:
return value
Expand Down Expand Up @@ -100,31 +104,36 @@ def __init__(self, enum_cls: type[T_StrEnum], **opts: Any) -> None:
super().__init__(length=64, **opts)
self._enum_cls = enum_cls

@override
def process_bind_param( # type: ignore[override]
self,
value: T_StrEnum | None,
dialect: Dialect,
) -> str | None:
return value.value if value is not None else None

@override
def process_result_value( # type: ignore[override]
self,
value: Any | None,
dialect: Dialect,
) -> T_StrEnum | None:
return self._enum_cls(value) if value is not None else None

@override
def copy(self, **kw: Any) -> Self:
return StrEnumType(self._enum_cls, **self._opts) # type: ignore[return-value]

@property
@override
def python_type(self) -> type[T_StrEnum]:
return self._enum_cls


class PasswordColumn(TypeDecorator[str]):
impl = VARCHAR

@override
def process_bind_param(self, value: Any, dialect: Dialect) -> str:
return hash_password(value)

Expand Down
2 changes: 2 additions & 0 deletions src/ai/backend/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
Literal,
ParamSpec,
cast,
override,
)
from uuid import UUID

Expand Down Expand Up @@ -950,6 +951,7 @@ def __init__(
self._sync_container_lifecycle_observer = SyncContainerLifecycleObserver.instance()
self._clean_kernel_registry_task = asyncio.create_task(self._clean_kernel_registry_loop())

@override
async def __ainit__(self) -> None:
"""
An implementation of AbstractAgent would define its own ``__ainit__()`` method.
Expand Down
7 changes: 7 additions & 0 deletions src/ai/backend/agent/alloc_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Any,
TypeVar,
final,
override,
)

import attr
Expand Down Expand Up @@ -299,6 +300,7 @@ def __init__(
}
super().__init__(*args, **kwargs)

@override
def allocate(
self,
slots: Mapping[SlotName, Decimal],
Expand Down Expand Up @@ -479,6 +481,7 @@ def _allocate_evenly(

return allocation

@override
def apply_allocation(
self,
existing_alloc: Mapping[SlotName, Mapping[DeviceId, Decimal]],
Expand All @@ -487,6 +490,7 @@ def apply_allocation(
for device_id, alloc in per_device_alloc.items():
self.allocations[slot_name][device_id] += alloc

@override
def free(
self,
existing_alloc: Mapping[SlotName, Mapping[DeviceId, Decimal]],
Expand Down Expand Up @@ -516,6 +520,7 @@ def __init__(
self.digits = Decimal(10) ** -2 # decimal points that is supported by agent
self.powers = Decimal(100) # reciprocal of self.digits

@override
def allocate(
self,
slots: Mapping[SlotName, Decimal],
Expand Down Expand Up @@ -917,6 +922,7 @@ def allocate_across_devices(
self.update_affinity_hint(slot_allocation, affinity_hint)
return allocation

@override
def apply_allocation(
self,
existing_alloc: Mapping[SlotName, Mapping[DeviceId, Decimal]],
Expand All @@ -925,6 +931,7 @@ def apply_allocation(
for device_id, alloc in per_device_alloc.items():
self.allocations[slot_name][device_id] += alloc

@override
def free(
self,
existing_alloc: Mapping[SlotName, Mapping[DeviceId, Decimal]],
Expand Down
4 changes: 3 additions & 1 deletion src/ai/backend/agent/cli/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from contextlib import AbstractContextManager
from pathlib import Path
from typing import TYPE_CHECKING, Any, Self
from typing import TYPE_CHECKING, Any, Self, override

import click

Expand All @@ -19,6 +19,7 @@ def __init__(self, log_level: LogLevel, config_path: Path | None = None) -> None
self.config_path = config_path
self.log_level = log_level

@override
def __enter__(self) -> Self:
from ai.backend.logging import LocalLogger

Expand All @@ -31,6 +32,7 @@ def __enter__(self) -> Self:
self._logger.__enter__()
return self

@override
def __exit__(self, *exc_info: Any) -> None:
click_ctx = click.get_current_context()
if click_ctx.invoked_subcommand != "start-server":
Expand Down
3 changes: 3 additions & 0 deletions src/ai/backend/agent/dependencies/bootstrap/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import override

from ai.backend.agent.config.unified import AgentUnifiedConfig
from ai.backend.common.configs.redis import RedisConfig
Expand Down Expand Up @@ -50,10 +51,12 @@ class AgentBootstrapComposer(DependencyComposer[AgentBootstrapInput, AgentBootst
"""

@property
@override
def stage_name(self) -> str:
return "bootstrap"

@asynccontextmanager
@override
async def compose(
self,
stack: DependencyStack,
Expand Down
3 changes: 3 additions & 0 deletions src/ai/backend/agent/dependencies/bootstrap/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import override

from ai.backend.agent.config.unified import AgentConfigValidationContext, AgentUnifiedConfig
from ai.backend.common import config as common_config
Expand Down Expand Up @@ -35,10 +36,12 @@ class AgentConfigLoaderDependency(
"""

@property
@override
def stage_name(self) -> str:
return "config-loader"

@asynccontextmanager
@override
async def provide(
self, setup_input: AgentConfigLoaderInput
) -> AsyncIterator[AgentUnifiedConfig]:
Expand Down
4 changes: 4 additions & 0 deletions src/ai/backend/agent/dependencies/bootstrap/etcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import override

from ai.backend.agent.config.unified import AgentUnifiedConfig
from ai.backend.common.dependencies import DependencyProvider
Expand All @@ -18,10 +19,12 @@ class AgentEtcdDependency(DependencyProvider[AgentUnifiedConfig, AsyncEtcd]):
"""

@property
@override
def stage_name(self) -> str:
return "etcd"

@asynccontextmanager
@override
async def provide(self, setup_input: AgentUnifiedConfig) -> AsyncIterator[AsyncEtcd]:
"""Initialize and provide etcd client.

Expand Down Expand Up @@ -60,6 +63,7 @@ async def provide(self, setup_input: AgentUnifiedConfig) -> AsyncIterator[AsyncE
) as etcd:
yield etcd

@override
def gen_liveness_checker(self, resource: AsyncEtcd) -> ServiceHealthChecker:
"""Liveness — stuck etcd connection observed; restart is the recovery path."""
return EtcdHealthChecker(etcd=resource)
3 changes: 3 additions & 0 deletions src/ai/backend/agent/dependencies/bootstrap/redis_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import override

from ai.backend.common import config as common_config
from ai.backend.common.configs.redis import RedisConfig
Expand All @@ -16,10 +17,12 @@ class RedisConfigDependency(NonMonitorableDependencyProvider[AsyncEtcd, RedisCon
"""

@property
@override
def stage_name(self) -> str:
return "redis-config (temp)"

@asynccontextmanager
@override
async def provide(self, setup_input: AsyncEtcd) -> AsyncIterator[RedisConfig]:
"""Read redis config from etcd.

Expand Down
3 changes: 3 additions & 0 deletions src/ai/backend/agent/dependencies/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import override

from ai.backend.common.dependencies import DependencyComposer, DependencyStack
from ai.backend.logging.types import LogLevel
Expand Down Expand Up @@ -48,10 +49,12 @@ class AgentDependencyComposer(DependencyComposer[AgentDependencyInput, AgentDepe
"""

@property
@override
def stage_name(self) -> str:
return "agent-dependencies"

@asynccontextmanager
@override
async def compose(
self,
stack: DependencyStack,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import override

from aiodocker import Docker

Expand Down Expand Up @@ -51,10 +52,12 @@ class AgentInfrastructureComposer(
"""

@property
@override
def stage_name(self) -> str:
return "infrastructure"

@asynccontextmanager
@override
async def compose(
self,
stack: DependencyStack,
Expand Down
Loading
Loading