Skip to content
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

2.x/logging #71

Merged
merged 8 commits into from
May 3, 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
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ repos:
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
exclude: "kubeconf"
- id: debug-statements
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.2
rev: v0.4.2
hooks:
- id: ruff
pass_filenames: false
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
Expand Down
2 changes: 1 addition & 1 deletion kubeconf/sercets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ stringData:
db_host: "postgres.default.svc.cluster.local"
db_port: ""
auth0_client_id: ""
auth0_client_secret: ""
auth0_client_secret: ""
16 changes: 16 additions & 0 deletions kubeconf/service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,19 @@ spec:
targetPort: 80
selector:
app: costy-app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: costy-ingress
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: costy-service
port:
number: 80
81 changes: 72 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name="costy"
version="0.0.1"
name = "costy"
version = "0.0.1"
dependencies = [
'sqlalchemy',
'litestar',
Expand All @@ -13,7 +13,8 @@ dependencies = [
'alembic',
'adaptix',
'aiohttp',
'python-jose'
'python-jose',
'prometheus-client'
]

[project.optional-dependencies]
Expand All @@ -35,11 +36,73 @@ dev = [
]

[tool.isort]
profile = "black"
line_length = 79
multi_line_output = 3
line_length = 120
include_trailing_comma = true
combine_as_imports = true
remove_redundant_aliases = true

[tool.black]
line-length = 79
target-version = ['py310']

[tool.pytest_env]
[tool.ruff]
include = ['src']
exclude = ['migrations']
line-length = 120

[tool.ruff.lint]
select = ['ALL']
fixable = [
'Q000',
'Q001',
'COM812',
'D400',
'PT001',
]
ignore = [
# Rules that should be turned on in the near future
'D',
'N818',
'B904',
'FIX002',
'RUF012',
'S311',
'DTZ005',
'DTZ006',
# Rules emitting false alerts
'N804',
'B008',
'BLE001',
'RUF009',
'I001',
# Rules that are not applicable in the project for now
'TID252',
'D104',
'ANN',
'SLF001',
'ARG',
'D100',
'PLR0913',
'TCH002',
# Strange and obscure rules that will never be turned on
'ANN101',
'FA100',
'TRY003',
'TRY201',
'EM',
'PERF203',
'TCH001',
'TD002',
'PTH201',
'RSE102',
'FA102',
'TD003',
'PTH123'
]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ['F401']

[tool.ruff.lint.pyupgrade]
keep-runtime-typing = true

[tool.ruff.lint.flake8-pytest-style]
parametrize-names-type = "list"
18 changes: 13 additions & 5 deletions src/costy/adapters/auth/auth_gateway.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from httpx import AsyncClient
from sqlalchemy import Table
from sqlalchemy.ext.asyncio import AsyncSession
Expand All @@ -6,15 +8,19 @@
from costy.domain.exceptions.access import AuthenticationError, RegisterError
from costy.infrastructure.config import AuthSettings

logger = logging.getLogger(__name__)


class AuthGateway(AuthLoger, AuthRegister):
AUTH_SUCCESS_CODE = 200
REGISTER_SUCCESS_CODE = 200

def __init__(
self,
db_session: AsyncSession,
web_session: AsyncClient,
table: Table,
settings: AuthSettings
settings: AuthSettings,
) -> None:
self.db_session = db_session
self.web_session = web_session
Expand All @@ -29,14 +35,15 @@
"client_id": self.settings.client_id,
"client_secret": self.settings.client_secret,
"audience": self.settings.audience,
"grant_type": self.settings.grant_type
"grant_type": self.settings.grant_type,
}
response = await self.web_session.post(url, data=data)
response_data = response.json()
if response.status_code == 200:
if response.status_code == self.AUTH_SUCCESS_CODE:
token: str | None = response_data.get("access_token")
if token:
return token
logger.info("Authentication failed: %s", response_data)

Check warning on line 46 in src/costy/adapters/auth/auth_gateway.py

View check run for this annotation

Codecov / codecov/patch

src/costy/adapters/auth/auth_gateway.py#L46

Added line #L46 was not covered by tests
raise AuthenticationError(response_data)

async def register(self, email: str, password: str) -> str:
Expand All @@ -46,10 +53,11 @@
"password": password,
"client_id": self.settings.client_id,
"client_secret": self.settings.client_secret,
"connection": self.settings.connection
"connection": self.settings.connection,
}
response = await self.web_session.post(url, data=data)
response_data = response.json()
if response.status_code == 200:
if response.status_code == self.REGISTER_SUCCESS_CODE:

Check warning on line 60 in src/costy/adapters/auth/auth_gateway.py

View check run for this annotation

Codecov / codecov/patch

src/costy/adapters/auth/auth_gateway.py#L60

Added line #L60 was not covered by tests
return response_data["_id"]
logger.info("Register failed: %s", response_data)

Check warning on line 62 in src/costy/adapters/auth/auth_gateway.py

View check run for this annotation

Codecov / codecov/patch

src/costy/adapters/auth/auth_gateway.py#L62

Added line #L62 was not covered by tests
raise RegisterError(response_data)
38 changes: 22 additions & 16 deletions src/costy/adapters/auth/token.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging
from datetime import datetime, timedelta
from typing import Any, Literal

from httpx import AsyncClient
from jose import exceptions as jwt_exc
from jose import jwt
from jose import exceptions as jwt_exc, jwt

from costy.application.common.id_provider import IdProvider
from costy.application.common.user.user_gateway import UserReader
Expand All @@ -15,13 +15,15 @@
"RS256", "RS384", "RS512",
]

logger = logging.getLogger(__name__)


class JwtTokenProcessor:
def __init__(
self,
algorithm: Algorithm,
audience: str,
issuer: str,
self,
algorithm: Algorithm,
audience: str,
issuer: str,
):
self.algorithm = algorithm
self.audience = audience
Expand All @@ -36,19 +38,20 @@
"kid": key["kid"],
"use": key["use"],
"n": key["n"],
"e": key["e"]
"e": key["e"],
}
return rsa_key

def validate_token(self, token: str, jwks: dict[Any, Any]) -> str:
invalid_header_error = AuthenticationError(
{"detail": "Invalid header. Use an RS256 signed JWT Access Token"}
{"detail": "Invalid header. Use an RS256 signed JWT Access Token"},
)
try:
unverified_header = jwt.get_unverified_header(token)
except jwt_exc.JWTError:
raise invalid_header_error
if unverified_header["alg"] == "HS256":
logger.info("Token decode error. Invalid encode algorithm: %s", unverified_header["alg"])

Check warning on line 54 in src/costy/adapters/auth/token.py

View check run for this annotation

Codecov / codecov/patch

src/costy/adapters/auth/token.py#L54

Added line #L54 was not covered by tests
raise invalid_header_error
rsa_key = self._fetch_rsa_key(jwks, unverified_header)
try:
Expand All @@ -57,18 +60,21 @@
rsa_key,
algorithms=[self.algorithm],
audience=self.audience,
issuer=self.issuer
issuer=self.issuer,
)
return payload["sub"].replace("auth0|", "")
except jwt_exc.ExpiredSignatureError:
raise AuthenticationError({"detail": "token is expired"})
except jwt_exc.JWTClaimsError:
message = "incorrect claims (check audience and issuer)"
logger.exception("Auth token resolving fail. Message: %s", message)

Check warning on line 70 in src/costy/adapters/auth/token.py

View check run for this annotation

Codecov / codecov/patch

src/costy/adapters/auth/token.py#L69-L70

Added lines #L69 - L70 were not covered by tests
raise AuthenticationError(
{"detail": "incorrect claims (check audience and issuer)"}
{"detail": "incorrect claims (check audience and issuer)"},
)
except Exception:
except Exception as e:
logger.warning("Auth token resolving unknown error. Message: %s", e.args)

Check warning on line 75 in src/costy/adapters/auth/token.py

View check run for this annotation

Codecov / codecov/patch

src/costy/adapters/auth/token.py#L74-L75

Added lines #L74 - L75 were not covered by tests
raise AuthenticationError(
{"detail": "Unable to parse authentication token."}
{"detail": "Unable to parse authentication token."},
)


Expand Down Expand Up @@ -96,10 +102,10 @@

class TokenIdProvider(IdProvider):
def __init__(
self,
token_processor: JwtTokenProcessor,
key_set_provider: KeySetProvider,
token: str | None = None
self,
token_processor: JwtTokenProcessor,
key_set_provider: KeySetProvider,
token: str | None = None,
):
self.token_processor = token_processor
self.key_set_provider = key_set_provider
Expand Down
10 changes: 5 additions & 5 deletions src/costy/adapters/bankapi/bank_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
class BankGateway(Protocol):
@abstractmethod
async def fetch_operations(
self,
access_data: dict,
user_id: UserId,
from_time: datetime | None = None
) -> list[BankOperationDTO]:
self,
access_data: dict,
user_id: UserId,
from_time: datetime | None = None,
) -> list[BankOperationDTO] | None:
raise NotImplementedError
8 changes: 4 additions & 4 deletions src/costy/adapters/bankapi/bankapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class BankAPIGateway(
BankAPIReader,
BanksAPIReader,
BankAPIBulkUpdater,
BankAPIOperationsReader
BankAPIOperationsReader,
):
def __init__(
self,
Expand All @@ -37,7 +37,7 @@ def __init__(
table: Table,
retort: Retort,
bank_gateways: dict[str, BankGateway],
banks_info: dict[str, dict]
banks_info: dict[str, dict],
) -> None:
self._db_session = db_session
self._web_session = web_session
Expand All @@ -52,7 +52,7 @@ async def get_bankapi(self, bankapi_id: BankApiId) -> BankAPI | None:
return self._retort.load(result, BankAPI) if result else None

async def save_bankapi(self, bankapi: BankAPI) -> None:
retort = self._retort.extend(recipe=[name_mapping(BankAPI, skip=['id'])])
retort = self._retort.extend(recipe=[name_mapping(BankAPI, skip=["id"])])
values = retort.dump(bankapi)
query = insert(self._table).values(**values)
result = await self._db_session.execute(query)
Expand Down Expand Up @@ -85,7 +85,7 @@ async def update_bankapis(self, bankapis: list[BankAPI]) -> None:
for stmt in stmts:
await self._db_session.execute(stmt)

async def read_bank_operations(self, bankapi: BankAPI) -> list[BankOperationDTO]:
async def read_bank_operations(self, bankapi: BankAPI) -> list[BankOperationDTO] | None:
bank_gateway = self._bank_gateways[bankapi.name]
from_time = datetime.fromtimestamp(bankapi.updated_at) if bankapi.updated_at else None
return await bank_gateway.fetch_operations(bankapi.access_data, bankapi.user_id, from_time)
Loading
Loading