diff --git a/ruff.toml b/ruff.toml index e789980ef..75cfbf326 100644 --- a/ruff.toml +++ b/ruff.toml @@ -3,7 +3,7 @@ line-length = 120 target-version = "py311" [lint] -select = ["E", "F", "W", "I", "ASYNC", "UP", "FLY", "PERF", "FURB", "ERA", "ANN", "PIE", "PL"] +select = ["E", "F", "W", "I", "ASYNC", "UP", "FLY", "PERF", "FURB", "ERA", "ANN", "PIE", "PL", "PGH", "FAST"] ignore = ["E501", "PLR", "PLW"] diff --git a/src/eduid/userdb/db/sync_db.py b/src/eduid/userdb/db/sync_db.py index 0fb13271d..ea21026ae 100644 --- a/src/eduid/userdb/db/sync_db.py +++ b/src/eduid/userdb/db/sync_db.py @@ -316,17 +316,6 @@ def setup_indexes(self, indexes: Mapping[str, Any]) -> None: params["name"] = name self._coll.create_index(key, **params) - def legacy_save(self, doc: dict[str, Any]) -> str: - """ - Only used in tests and should probably be removed when time allows. - pymongo removed the save method in version 4.0. - """ - if "_id" in doc: - self._coll.replace_one({"_id": doc["_id"]}, doc, upsert=True) - return doc["_id"] - res = self._coll.insert_one(doc) # type: ignore - return res.inserted_id - def _save( self, data: TUserDbDocument, diff --git a/src/eduid/userdb/element.py b/src/eduid/userdb/element.py index afc707de1..e2a390b9b 100644 --- a/src/eduid/userdb/element.py +++ b/src/eduid/userdb/element.py @@ -51,7 +51,7 @@ from collections.abc import Mapping from datetime import datetime from enum import Enum -from typing import Any, Generic, NewType, TypeVar +from typing import Any, Generic, NewType, TypeVar, cast from pydantic import BaseModel, ConfigDict, Field, field_validator @@ -399,10 +399,7 @@ def verified(self) -> list[ListElement]: Get all the verified elements in the ElementList. """ - verified_elements = [e for e in self.elements if isinstance(e, VerifiedElement) and e.is_verified] - # mypy figures out the real type of `verified_elements' since isinstance() is used above and complains - # error: Incompatible return value type (got "List[VerifiedElement]", expected "List[ListElement]") - return verified_elements # type: ignore + return cast(list[ListElement], [e for e in self.elements if isinstance(e, VerifiedElement) and e.is_verified]) class PrimaryElementList(VerifiedElementList[ListElement], Generic[ListElement], ABC): @@ -450,9 +447,7 @@ def primary(self) -> ListElement | None: if not isinstance(match, PrimaryElement): raise UserDBValueError(f"Primary element {repr(match)} is not of type PrimaryElement") - # mypy figures out the real type of match since isinstance() is used above and complains - # error: Incompatible return value type (got "PrimaryElement", expected "Optional[ListElement]") - return match # type: ignore + return cast(ListElement, match) def set_primary(self, key: ElementKey) -> None: """ @@ -483,10 +478,7 @@ def set_primary(self, key: ElementKey) -> None: raise UserDBValueError(f"Element {repr(this)} is not of type PrimaryElement") this.is_primary = bool(this.key == key) new += [this] - # mypy figures out the real type of `new' since isinstance() is used above and complains - # error: Incompatible types in assignment (expression has type "List[PrimaryElement]", - # variable has type "List[ListElement]") - self.elements = new # type: ignore + self.elements = cast(list[ListElement], new) @classmethod def _get_primary(cls, elements: list[ListElement]) -> ListElement | None: @@ -512,9 +504,7 @@ def _get_primary(cls, elements: list[ListElement]) -> ListElement | None: if not primary.is_verified: raise PrimaryElementViolation("Primary element is not verified") - # mypy figures out the real type of `res[0]' since isinstance() is used above and complains - # error: Incompatible return value type (got "PrimaryElement", expected "Optional[ListElement]") - return res[0] # type: ignore + return cast(ListElement, primary) def remove(self, key: ElementKey) -> None: """ diff --git a/src/eduid/vccs/server/endpoints/add_creds.py b/src/eduid/vccs/server/endpoints/add_creds.py index 542cd1d8d..12e3ab5d1 100644 --- a/src/eduid/vccs/server/endpoints/add_creds.py +++ b/src/eduid/vccs/server/endpoints/add_creds.py @@ -1,4 +1,5 @@ import json +from typing import Annotated from fastapi import APIRouter, Form, Request from pydantic.main import BaseModel @@ -28,8 +29,8 @@ class AddCredsFormResponse(BaseModel): add_creds_response: AddCredsResponseV1 -@add_creds_router.post("/add_creds", response_model=AddCredsFormResponse) -async def add_creds_legacy(req: Request, request: str = Form(...)) -> AddCredsFormResponse: +@add_creds_router.post("/add_creds") +async def add_creds_legacy(req: Request, request: Annotated[str, Form(...)]) -> AddCredsFormResponse: req.app.logger.debug(f"Add credentials (using form): {request}") class AddCredsInnerRequest(BaseModel): diff --git a/src/eduid/vccs/server/endpoints/authenticate.py b/src/eduid/vccs/server/endpoints/authenticate.py index 5c54753b4..bc1a8d1b8 100644 --- a/src/eduid/vccs/server/endpoints/authenticate.py +++ b/src/eduid/vccs/server/endpoints/authenticate.py @@ -1,4 +1,5 @@ import json +from typing import Annotated from fastapi import APIRouter, Form, Request from pydantic.main import BaseModel @@ -29,8 +30,8 @@ class AuthenticateFormResponse(BaseModel): auth_response: AuthenticateResponseV1 -@authenticate_router.post("/authenticate", response_model=AuthenticateFormResponse) -async def authenticate_legacy(req: Request, request: str = Form(...)) -> AuthenticateFormResponse: +@authenticate_router.post("/authenticate") +async def authenticate_legacy(req: Request, request: Annotated[str, Form(...)]) -> AuthenticateFormResponse: req.app.logger.debug(f"Authenticate (using form): {request}") class AuthenticateInnerRequest(BaseModel): @@ -48,7 +49,7 @@ class AuthenticateInnerRequest(BaseModel): return response -@authenticate_router.post("/v2/authenticate", response_model=AuthenticateResponseV1) +@authenticate_router.post("/v2/authenticate") async def authenticate(req: Request, request: AuthenticateRequestV1) -> AuthenticateResponseV1: """ Handle a password authentication request, along the following pseudo-code : diff --git a/src/eduid/vccs/server/endpoints/misc.py b/src/eduid/vccs/server/endpoints/misc.py index 1848b1426..31f037e9a 100644 --- a/src/eduid/vccs/server/endpoints/misc.py +++ b/src/eduid/vccs/server/endpoints/misc.py @@ -19,7 +19,7 @@ class StatusResponse(BaseModel): version: int = 1 -@misc_router.get("/status/healthy", response_model=StatusResponse) +@misc_router.get("/status/healthy") async def status(request: Request) -> StatusResponse: _test_keyhandle = request.app.state.config.add_creds_password_key_handle res = StatusResponse(status=Status.OK) @@ -42,7 +42,7 @@ class HMACResponse(BaseModel): hmac: str -@misc_router.get("/hmac/{keyhandle}/{data}", response_model=HMACResponse) +@misc_router.get("/hmac/{keyhandle}/{data}") async def hmac(request: Request, keyhandle: int, data: bytes) -> HMACResponse: hmac = await request.app.state.hasher.hmac_sha1(key_handle=keyhandle, data=data) return HMACResponse(keyhandle=keyhandle, hmac=hmac.hex()) diff --git a/src/eduid/vccs/server/endpoints/revoke_creds.py b/src/eduid/vccs/server/endpoints/revoke_creds.py index 93db930e7..be9e4b314 100644 --- a/src/eduid/vccs/server/endpoints/revoke_creds.py +++ b/src/eduid/vccs/server/endpoints/revoke_creds.py @@ -1,4 +1,5 @@ import json +from typing import Annotated from fastapi import APIRouter, Form, Request from pydantic.main import BaseModel @@ -28,8 +29,8 @@ class RevokeCredsFormResponse(BaseModel): revoke_creds_response: RevokeCredsResponseV1 -@revoke_creds_router.post("/revoke_creds", response_model=RevokeCredsFormResponse) -async def revoke_creds_legacy(req: Request, request: str = Form(...)) -> RevokeCredsFormResponse: +@revoke_creds_router.post("/revoke_creds") +async def revoke_creds_legacy(req: Request, request: Annotated[str, Form(...)]) -> RevokeCredsFormResponse: req.app.logger.debug(f"Revoke credentials (using form): {request}") class RevokeCredsInnerRequest(BaseModel): diff --git a/src/eduid/webapp/authn/app.py b/src/eduid/webapp/authn/app.py index ef344552b..804572180 100644 --- a/src/eduid/webapp/authn/app.py +++ b/src/eduid/webapp/authn/app.py @@ -1,5 +1,5 @@ from collections.abc import Mapping -from typing import Any +from typing import Any, cast from flask import current_app @@ -20,7 +20,7 @@ def __init__(self, config: AuthnConfig, **kwargs: Any) -> None: def get_current_app() -> AuthnApp: """Teach pycharm about AuthnApp""" - return current_app # type: ignore + return cast(AuthnApp, current_app) current_authn_app = get_current_app() diff --git a/src/eduid/webapp/common/api/decorators.py b/src/eduid/webapp/common/api/decorators.py index 48c6c3f5f..7476e6a38 100644 --- a/src/eduid/webapp/common/api/decorators.py +++ b/src/eduid/webapp/common/api/decorators.py @@ -245,6 +245,6 @@ def unmarshal_decorator( if isinstance(ret, FluxData): raise TypeError("Wrong order of decorators, UnmarshalWith must be the first decorator") # Uh, don't know how to check for Awaitable[FluxData], so for now we just ignore the type error below - return ret # type: ignore + return ret # type: ignore[return-value] return unmarshal_decorator diff --git a/src/eduid/webapp/common/authn/utils.py b/src/eduid/webapp/common/authn/utils.py index d325b683a..ef96c926d 100644 --- a/src/eduid/webapp/common/authn/utils.py +++ b/src/eduid/webapp/common/authn/utils.py @@ -38,7 +38,7 @@ def get_saml2_config(module_path: str, name: str = "SAML_CONFIG") -> SPConfig: if spec is None: raise RuntimeError(f"Failed loading saml2_settings module: {module_path}") module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) # type: ignore + spec.loader.exec_module(module) # type: ignore[union-attr] conf = SPConfig() conf.load(module.__getattribute__(name)) diff --git a/src/eduid/workers/am/tests/test_index.py b/src/eduid/workers/am/tests/test_index.py index ebd0ed936..fa106b68d 100644 --- a/src/eduid/workers/am/tests/test_index.py +++ b/src/eduid/workers/am/tests/test_index.py @@ -24,7 +24,7 @@ def test_index_setup(self) -> None: "mobile-index-v1": {"key": [("mobile.mobile", 1), ("mobile.verified", 1)]}, "mailAliases-index-v1": {"key": [("mailAliases.email", 1), ("mailAliases.verified", 1)]}, } - db = UserDB(self.settings.mongo_uri) # type: ignore + db = UserDB(self.settings.mongo_uri) # type: ignore[call-arg,var-annotated,attr-defined] print(db._coll.index_information()) db.setup_indexes(indexes) current_indexes = db._coll.index_information()