Skip to content

Fix linting errors #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 10, 2024
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dev-deps: deps

fmt:
ruff format $(SOURCES)
ruff check $(SOURCES) --fix --unsafe-fixes
ruff check --select I --fix $(SOURCES)

lint:
dotenv-linter env.example
Expand Down
42 changes: 41 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,56 @@ dev = [

[tool.ruff]
line-length = 160
src = ["src"]


[tool.roof.lint]
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN101", # Missing type annotation for self in method
"ANN102", # missing type annotation for `cls` in classmethod
"ANN401", # dynamically typed expressions (typing.Any) are disallowed in `{}`
"COM812", # Trailing comma missing
"D100", # missing docstring in public module
"D101", # missing docstring in public class
"D102", # missing docstring in public method
"D103", # missing docstring in public function
"D104", # missing docstring in public package
"D105", # missing docstring in magic method
"D106", # missing docstring in public nested class
"D107", # missing docstring in `__init__`
"D203", # one blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
"EM101", # exception must not use a string literal, assign to variable first
"EM102", # expection must not use an f-string literal, assign to variable first
"INP001", # file `%filename%` is part of an implicit namespace package. Add an `__init__.py`
"ISC001", # implicitly concatenated string literals on one line
"N818", # exception name `{}` should be named with an Error suffix
"PT001", # use `@pytest.fixture()` over `@pytest.fixture`
"TRY003", # avoid specifying long messages outside the exception class
]


[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"


[tool.ruff.lint.per-file-ignores]
"**/tests/*" = [
"ANN", # flake8-annotations
"ARG001", # Unused function argument
"PLR0913", # Too many arguments in function definition
"PLR2004", # Magic value used in comparison, consider replacing `%value%` with a constant variable
"S101", # Use of `assert` detected
]
"**/fixtures.py" = [
"ANN", # flake8-annotations
]

[tool.ruff.lint.isort]
extra-standard-library = ["pytest"]


[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["src"]
Expand Down
18 changes: 8 additions & 10 deletions src/a12n/jwk_client.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import asyncio
from dataclasses import dataclass
import json
import logging
from dataclasses import dataclass

import httpx
import jwt
from jwt.api_jwk import PyJWK
from jwt.api_jwk import PyJWKSet
from jwt.api_jwk import PyJWK, PyJWKSet
from jwt.api_jwt import decode_complete as decode_token
from jwt.exceptions import PyJWKSetError
from jwt.jwk_set_cache import JWKSetCache


from app.types import DecodedValidToken


logger = logging.getLogger(__name__)


Expand All @@ -24,7 +21,8 @@ class AsyncJWKClientException(Exception):

@dataclass
class AsyncJWKClient:
"""
"""Async JW Keys client.

Inspired and partially copy-pasted from 'jwt.jwks_client.PyJWKClient'.
The purpose is the same but querying the JWKS endpoint is async.
"""
Expand Down Expand Up @@ -68,7 +66,7 @@ async def fetch_data(self) -> PyJWKSet:
except PyJWKSetError as exc:
raise AsyncJWKClientException(exc) from exc

async def get_jwk_set(self, refresh: bool = False) -> PyJWKSet:
async def get_jwk_set(self, *, refresh: bool = False) -> PyJWKSet:
jwk_set: PyJWKSet | None = None

while self.fetch_data_lock.locked():
Expand All @@ -82,8 +80,8 @@ async def get_jwk_set(self, refresh: bool = False) -> PyJWKSet:

return jwk_set

async def get_signing_keys(self, refresh: bool = False) -> list[PyJWK]:
jwk_set = await self.get_jwk_set(refresh)
async def get_signing_keys(self, *, refresh: bool = False) -> list[PyJWK]:
jwk_set = await self.get_jwk_set(refresh=refresh)

signing_keys = [
jwk_set_key
Expand All @@ -102,7 +100,7 @@ async def get_signing_keys(self, refresh: bool = False) -> list[PyJWK]:
return signing_keys

async def get_signing_key(self, kid: str) -> PyJWK:
signing_keys = await self.get_signing_keys()
signing_keys = await self.get_signing_keys(refresh=False)
signing_key = self.match_kid(signing_keys, kid)

if not signing_key:
Expand Down
11 changes: 5 additions & 6 deletions src/a12n/tests/async_jwk_client/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest

from respx import MockRouter
from respx import Route
from respx import MockRouter, Route

from a12n.jwk_client import AsyncJWKClient

Expand All @@ -10,13 +9,13 @@

@pytest.fixture
def expired_token():
return "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjNMcjhuTjh1R29wUElMZlFvUGpfRCJ9.eyJpc3MiOiJodHRwczovL2Rldi1wcm50ZG1vMTYzc2NsczR4LnVzLmF1dGgwLmNvbS8iLCJhdWQiOiIxV1NiR1hGUnlhS0NsTHJvWHZteTlXdndrZUtHb1JvayIsImlhdCI6MTY5ODUyODU1OSwiZXhwIjoxNjk4NTI4ODU5LCJzdWIiOiJhdXRoMHw2NTNjMzI2MGEzMDQ0OGM1OTRhNjllMTIiLCJzaWQiOiI5MEZ3WFNDSFUtd0N3QmY0Y1YyQ3NZTnpBMldieDNUcSIsIm5vbmNlIjoiZTIxZWVhNTljNGY1MDg0N2Q3YzFhOGUzZjQ0NjVjYTcifQ.FO_xoMA9RGI7uAVauv00-zdORgkvCwyWfeAPd7lmU_nKzGp5avPa2MN66S0fjLKOxb8tgzrfpXYLUhDl1nqUvtj1A54-PfNW0n0ctdn2zk_CCOxsAjKyImlIgq7Y4DIuil0wikj7FdoWkB-bCBrKs7JaOoWkSHws9uQxRyvZzBwPHExW0myHWvB3G0x8g23PfSv2oALbvXBp0OAniGwru2Br9e2iXCVyGAUMTCpQmjPDAyfeYXGxF9BhxuX3e-GL80oyngBQK0kTxw-2Xz8LDSC-MI2jTs1gUo9qdVrg_1fzQtvAW9LGaWg5L_CJe92ZH3l1fBPfSh7Gc6uBtwF-YA"
return "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjNMcjhuTjh1R29wUElMZlFvUGpfRCJ9.eyJpc3MiOiJodHRwczovL2Rldi1wcm50ZG1vMTYzc2NsczR4LnVzLmF1dGgwLmNvbS8iLCJhdWQiOiIxV1NiR1hGUnlhS0NsTHJvWHZteTlXdndrZUtHb1JvayIsImlhdCI6MTY5ODUyODU1OSwiZXhwIjoxNjk4NTI4ODU5LCJzdWIiOiJhdXRoMHw2NTNjMzI2MGEzMDQ0OGM1OTRhNjllMTIiLCJzaWQiOiI5MEZ3WFNDSFUtd0N3QmY0Y1YyQ3NZTnpBMldieDNUcSIsIm5vbmNlIjoiZTIxZWVhNTljNGY1MDg0N2Q3YzFhOGUzZjQ0NjVjYTcifQ.FO_xoMA9RGI7uAVauv00-zdORgkvCwyWfeAPd7lmU_nKzGp5avPa2MN66S0fjLKOxb8tgzrfpXYLUhDl1nqUvtj1A54-PfNW0n0ctdn2zk_CCOxsAjKyImlIgq7Y4DIuil0wikj7FdoWkB-bCBrKs7JaOoWkSHws9uQxRyvZzBwPHExW0myHWvB3G0x8g23PfSv2oALbvXBp0OAniGwru2Br9e2iXCVyGAUMTCpQmjPDAyfeYXGxF9BhxuX3e-GL80oyngBQK0kTxw-2Xz8LDSC-MI2jTs1gUo9qdVrg_1fzQtvAW9LGaWg5L_CJe92ZH3l1fBPfSh7Gc6uBtwF-YA" # noqa: E501


@pytest.fixture
def token():
# The token won't expire in ~100 years (expiration date 2123-10-05, it's more than enough to rely on it in test)
return "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjNMcjhuTjh1R29wUElMZlFvUGpfRCJ9.eyJpc3MiOiJodHRwczovL2Rldi1wcm50ZG1vMTYzc2NsczR4LnVzLmF1dGgwLmNvbS8iLCJhdWQiOiIxV1NiR1hGUnlhS0NsTHJvWHZteTlXdndrZUtHb1JvayIsImlhdCI6MTY5ODUyODE3MCwiZXhwIjo0ODUyMTI4MTcwLCJzdWIiOiJhdXRoMHw2NTNjMzI2MGEzMDQ0OGM1OTRhNjllMTIiLCJzaWQiOiI5MEZ3WFNDSFUtd0N3QmY0Y1YyQ3NZTnpBMldieDNUcSIsIm5vbmNlIjoiMTVhNWI2M2Y3MzI5MDcwMmU3MGViZmJlMDc5ODgxYmIifQ.FQYBaTnjKJHcskRl1WsB4kKQmyvXRcG8RDWlB2woSbzukZx7SnWghC1qRhYeqOLBUBpe3Iu_EzxgF26YDZJ28bKKNgL4fVmYak3jOg2nRP2lulrkF8USmkqT9Vx85hlIEVCisYOS6DJE0bHJL5WbHjCmDjQ6RGRyVZ3s6UPFXIwe2CMC_egAdWrsLYrgA1mqozQhwLJN2zSuObkDffkpHbX9XXB225v3-ryY-Rr0rPh9AOfKtEeMUEmNG0gsGyIbi0DoPDjAxlxCDx7ULVSChIKhUv4DKICqrqzHyopA7oE8LlpDbPTshQsL6L4u1EwUT7maP9VTcEQUTnp3Cu5msw"
return "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjNMcjhuTjh1R29wUElMZlFvUGpfRCJ9.eyJpc3MiOiJodHRwczovL2Rldi1wcm50ZG1vMTYzc2NsczR4LnVzLmF1dGgwLmNvbS8iLCJhdWQiOiIxV1NiR1hGUnlhS0NsTHJvWHZteTlXdndrZUtHb1JvayIsImlhdCI6MTY5ODUyODE3MCwiZXhwIjo0ODUyMTI4MTcwLCJzdWIiOiJhdXRoMHw2NTNjMzI2MGEzMDQ0OGM1OTRhNjllMTIiLCJzaWQiOiI5MEZ3WFNDSFUtd0N3QmY0Y1YyQ3NZTnpBMldieDNUcSIsIm5vbmNlIjoiMTVhNWI2M2Y3MzI5MDcwMmU3MGViZmJlMDc5ODgxYmIifQ.FQYBaTnjKJHcskRl1WsB4kKQmyvXRcG8RDWlB2woSbzukZx7SnWghC1qRhYeqOLBUBpe3Iu_EzxgF26YDZJ28bKKNgL4fVmYak3jOg2nRP2lulrkF8USmkqT9Vx85hlIEVCisYOS6DJE0bHJL5WbHjCmDjQ6RGRyVZ3s6UPFXIwe2CMC_egAdWrsLYrgA1mqozQhwLJN2zSuObkDffkpHbX9XXB225v3-ryY-Rr0rPh9AOfKtEeMUEmNG0gsGyIbi0DoPDjAxlxCDx7ULVSChIKhUv4DKICqrqzHyopA7oE8LlpDbPTshQsL6L4u1EwUT7maP9VTcEQUTnp3Cu5msw" # noqa: E501


@pytest.fixture
Expand All @@ -26,7 +25,7 @@ def matching_kid_data():
{
"kty": "RSA",
"use": "sig",
"n": "oIQkRCY4X-_ItMUPt65wVIGewOJfjMhlu6HG_rHik5-dTK0o6oyUne2Gevetn2Vrn8NSIaARobLZ8expuJBYDS121w_RloC6MCuzlc-j_nHj-BcBOCqGWPVwKX4un0HueD3aW3buqzYcmX_9LhdSE8ARyN0S9O6RbYWDCTKFhrRXtIP4wzP8vdPGXGurtGIiBbhVCK1LHG2lO5Gt8IIQ_DAcX6swnXCfbHwR1OXc9Do06o8c7ZsZdjMty5b4Fpv8rAKA-HTP_One4yhKtqCMYs3_gcTeQdHi-0w634VnpdzC_0f_MMzNIgvXC8VdJgkGpa6jLBp3mTqaFUdkAXFYlw",
"n": "oIQkRCY4X-_ItMUPt65wVIGewOJfjMhlu6HG_rHik5-dTK0o6oyUne2Gevetn2Vrn8NSIaARobLZ8expuJBYDS121w_RloC6MCuzlc-j_nHj-BcBOCqGWPVwKX4un0HueD3aW3buqzYcmX_9LhdSE8ARyN0S9O6RbYWDCTKFhrRXtIP4wzP8vdPGXGurtGIiBbhVCK1LHG2lO5Gt8IIQ_DAcX6swnXCfbHwR1OXc9Do06o8c7ZsZdjMty5b4Fpv8rAKA-HTP_One4yhKtqCMYs3_gcTeQdHi-0w634VnpdzC_0f_MMzNIgvXC8VdJgkGpa6jLBp3mTqaFUdkAXFYlw", # noqa: E501
"e": "AQAB",
"kid": "3Lr8nN8uGopPILfQoPj_D",
"x5t": "f93zLhSTsgVJiS9JA0x8sHkaLMg",
Expand All @@ -46,7 +45,7 @@ def not_matching_kid_data():
{
"kty": "RSA",
"use": "sig",
"n": "zB0xsH539lpLVejR6Hq1bHN3EzDt_0tJyr5JVHz3GSnNYAaZzkqL7HyLlhwttl7_bRyZJeZ8X6aasBxVK2JCDc9U-0KMJXmSoJs1oWYRo79DqdzCXK3ZYXcgkvI9OWF1qVx76vbZVwiRv5qUzpINdLnsX2CXChyd0LFkg14bYrSfdN-eMmG1PXtHZufeKG6HW17PFXS7OwesMQIfQ9kFfSvgFkJgkNM0o6NaeB-ZPDvzfKmmpBXjtGcze0A56NdQ7Z42DRDURROS82sPISrX-iAt93tZ1F0IW_U4niIYc6NFcWPPXpQpiVDDwdrz-L1H63mSUDSDFsWVcv2xWry6kQ",
"n": "zB0xsH539lpLVejR6Hq1bHN3EzDt_0tJyr5JVHz3GSnNYAaZzkqL7HyLlhwttl7_bRyZJeZ8X6aasBxVK2JCDc9U-0KMJXmSoJs1oWYRo79DqdzCXK3ZYXcgkvI9OWF1qVx76vbZVwiRv5qUzpINdLnsX2CXChyd0LFkg14bYrSfdN-eMmG1PXtHZufeKG6HW17PFXS7OwesMQIfQ9kFfSvgFkJgkNM0o6NaeB-ZPDvzfKmmpBXjtGcze0A56NdQ7Z42DRDURROS82sPISrX-iAt93tZ1F0IW_U4niIYc6NFcWPPXpQpiVDDwdrz-L1H63mSUDSDFsWVcv2xWry6kQ", # noqa: E501
"e": "AQAB",
"kid": "ICOpsXGmpNaDPiljjRjiE",
"x5t": "1GDK6kGV6HvZ1m_-VdSKIFNEtEU",
Expand Down
3 changes: 2 additions & 1 deletion src/a12n/tests/async_jwk_client/tests_async_jwk_decode.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
from contextlib import nullcontext as does_not_raise

from a12n.jwk_client import AsyncJWKClientException
import pytest

pytestmark = [
pytest.mark.usefixtures("mock_success_response"),
Expand Down
3 changes: 1 addition & 2 deletions src/app/conf/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from app.conf.settings import get_app_settings
from app.conf.settings import Settings
from app.conf.settings import Settings, get_app_settings

__all__ = [
"Settings",
Expand Down
5 changes: 2 additions & 3 deletions src/app/conf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from typing import Literal

from pydantic import AmqpDsn
from pydantic_settings import BaseSettings
from pydantic_settings import SettingsConfigDict
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
Expand Down Expand Up @@ -33,4 +32,4 @@ class Settings(BaseSettings):

@lru_cache
def get_app_settings() -> Settings:
return Settings() # type: ignore
return Settings() # type: ignore[call-arg]
7 changes: 4 additions & 3 deletions src/app/fixtures.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import pytest
from collections.abc import Callable

from app.testing import MockedWebSocketServerProtocol


@pytest.fixture
def create_ws():
def create_ws() -> Callable[[], MockedWebSocketServerProtocol]:
return lambda: MockedWebSocketServerProtocol()


@pytest.fixture
def ws(create_ws):
def ws(create_ws) -> MockedWebSocketServerProtocol:
return create_ws()


@pytest.fixture
def ya_ws(create_ws):
def ya_ws(create_ws) -> MockedWebSocketServerProtocol:
return create_ws()
9 changes: 5 additions & 4 deletions src/app/services.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from abc import ABC
from abc import abstractmethod
from typing import Any, Callable
from abc import ABC, abstractmethod
from collections.abc import Callable
from typing import Any


class BaseService(ABC):
"""This is a template of a a base service.
"""Template of a a base service.

All services in the app should follow this rules:
* Input variables should be done at the __init__ phase
* Service should implement a single entrypoint without arguments
Expand Down
8 changes: 3 additions & 5 deletions src/app/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def send(self, message: str) -> None: # type: ignore[override]
await asyncio.sleep(0)
await self.send_queue.put(message)

async def close(self, code: int = CloseCode.NORMAL_CLOSURE, reason: str = "") -> None:
async def close(self, code: int = CloseCode.NORMAL_CLOSURE, reason: str = "") -> None: # noqa: ARG002
self.state = State.CLOSED

async def wait_messages_to_be_sent(self) -> None:
Expand All @@ -46,10 +46,8 @@ async def count_sent_to_client(self) -> int:
def client_send(self, message: dict) -> None:
self.recv_queue.put_nowait(json.dumps(message))

async def client_recv(self, skip_count_first_messages=0) -> dict | None:
"""Convenient for testing.
Receive one message at time. First messages could be discarded with 'skip_count_first_messages' parameter.
"""
async def client_recv(self, skip_count_first_messages: int = 0) -> dict | None:
"""Skip 'skip_count_first_messages' messages and return the next one. Convenient for testing."""
await self.wait_messages_to_be_sent()

if self.send_queue.empty():
Expand Down
2 changes: 1 addition & 1 deletion src/app/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import NewType, NamedTuple
from typing import NamedTuple, NewType

UserId = NewType("UserId", str)
Event = NewType("Event", str)
Expand Down
4 changes: 2 additions & 2 deletions src/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from app.conf import get_app_settings
from app.conf import Settings, get_app_settings

pytest_plugins = [
"app.fixtures",
Expand All @@ -9,5 +9,5 @@


@pytest.fixture
def settings():
def settings() -> Settings:
return get_app_settings()
12 changes: 5 additions & 7 deletions src/consumer/consumer.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import asyncio
from dataclasses import dataclass
import logging
from dataclasses import dataclass
from typing import Protocol

import aio_pika
import websockets
from pydantic import ValidationError

from app import conf
from consumer.dto import ConsumedMessage
from consumer.dto import OutgoingMessage
from consumer.dto import ConsumedMessage, OutgoingMessage
from storage.subscription_storage import SubscriptionStorage
from pydantic import ValidationError
import websockets


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -61,7 +59,7 @@ def parse_message(raw_message: aio_pika.abc.AbstractIncomingMessage) -> Consumed
try:
return ConsumedMessage.model_validate_json(raw_message.body)
except ValidationError as exc:
logger.error("Consumed message not in expected format. Errors: %s", exc.errors())
logger.error("Consumed message not in expected format. Errors: %s", exc.errors()) # noqa: TRY400
return None

@staticmethod
Expand Down
5 changes: 3 additions & 2 deletions src/consumer/dto.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pydantic import BaseModel
from pydantic import ConfigDict
from typing import Literal

from pydantic import BaseModel, ConfigDict

from app.types import Event


Expand Down
6 changes: 3 additions & 3 deletions src/consumer/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import pytest
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from dataclasses import dataclass

from consumer.consumer import Consumer
from dataclasses import dataclass
from contextlib import asynccontextmanager
from typing import AsyncGenerator


@pytest.fixture(autouse=True)
Expand Down
2 changes: 1 addition & 1 deletion src/consumer/tests/tests_consumer_on_message.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from contextlib import nullcontext as does_not_raise
import json
import pytest
from contextlib import nullcontext as does_not_raise

from consumer.tests.conftest import MockedIncomingMessage

Expand Down
23 changes: 12 additions & 11 deletions src/entrypoint.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import asyncio
import logging
import signal

import websockets
import logging

from app import conf
from handlers import WebSocketsHandler
from handlers import WebSocketsAccessGuardian
from storage.subscription_storage import SubscriptionStorage
from consumer import Consumer
from handlers import WebSocketsAccessGuardian, WebSocketsHandler
from storage.subscription_storage import SubscriptionStorage

logging.basicConfig(level=logging.INFO)

Expand All @@ -27,14 +26,16 @@ async def app_runner(
consumer: Consumer,
stop_signal: asyncio.Future,
) -> None:
async with websockets.serve(
ws_handler=websockets_handler.websockets_handler,
host=settings.WEBSOCKETS_HOST,
port=settings.WEBSOCKETS_PORT,
async with (
websockets.serve(
ws_handler=websockets_handler.websockets_handler,
host=settings.WEBSOCKETS_HOST,
port=settings.WEBSOCKETS_PORT,
),
asyncio.TaskGroup() as task_group,
):
async with asyncio.TaskGroup() as task_group:
task_group.create_task(access_guardian.run(stop_signal))
task_group.create_task(consumer.consume(stop_signal))
task_group.create_task(access_guardian.run(stop_signal))
task_group.create_task(consumer.consume(stop_signal))


async def main() -> None:
Expand Down
Loading