From f8e97b9ae8ad0fbd4c0ad42e7c9a3e63b4f7defa Mon Sep 17 00:00:00 2001 From: MikeP Date: Fri, 3 Feb 2023 23:36:18 +0500 Subject: [PATCH 01/53] ping service initial --- ping/__main__.py | 6 +++ ping/asgi.py | 26 +++++++++++- ping/service/__init__.py | 8 ++++ ping/service/bootstrap.py | 18 ++++++++ ping/service/crud.py | 30 +++++++++----- ping/service/ping.py | 17 -------- ping/service/scheduler.py | 68 +++++++++++++++++++++++++++++++ ping/settings.py | 17 ++++++++ requirements.txt | 21 +++++----- tests/backend/api/test_fetcher.py | 1 - 10 files changed, 173 insertions(+), 39 deletions(-) delete mode 100644 ping/service/ping.py create mode 100644 ping/service/scheduler.py diff --git a/ping/__main__.py b/ping/__main__.py index e69de29..ef0a419 100644 --- a/ping/__main__.py +++ b/ping/__main__.py @@ -0,0 +1,6 @@ +from .asgi import ping_app +import uvicorn + + +if __name__ == "__main__": + uvicorn.run(ping_app) \ No newline at end of file diff --git a/ping/asgi.py b/ping/asgi.py index 4200270..6186b1e 100644 --- a/ping/asgi.py +++ b/ping/asgi.py @@ -1,4 +1,28 @@ -from fastapi import FastAPI +import asyncio +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from fastapi import FastAPI ping_app = FastAPI() + + +def foo(): + print("foo") + + +def bar(): + print("bar") + + +s: AsyncIOScheduler = AsyncIOScheduler() + + +@ping_app.get("/") +async def gg(): + s.add_job(foo, 'interval', seconds=3) + s.start() + + +@ping_app.get("/stop") +async def stop(): + s.shutdown() diff --git a/ping/service/__init__.py b/ping/service/__init__.py index e69de29..165842b 100644 --- a/ping/service/__init__.py +++ b/ping/service/__init__.py @@ -0,0 +1,8 @@ +from .crud import CrudServiceInterface +from .bootstrap import crud_service + + +__all__ = [ + "CrudServiceInterface", + "crud_service", +] \ No newline at end of file diff --git a/ping/service/bootstrap.py b/ping/service/bootstrap.py index e69de29..15eef97 100644 --- a/ping/service/bootstrap.py +++ b/ping/service/bootstrap.py @@ -0,0 +1,18 @@ +from .crud import CrudService, FakeCrudService +from .scheduler import ApSchedulerService, FakeSchedulerService + + +class Config: + fake: bool = False + + +def crud_service(): + if Config.fake: + return FakeCrudService() + return CrudService() + + +def scheduler_service(): + if Config.fake: + return FakeCrudService() + return ApSchedulerService() \ No newline at end of file diff --git a/ping/service/crud.py b/ping/service/crud.py index ebc689d..61ae103 100644 --- a/ping/service/crud.py +++ b/ping/service/crud.py @@ -1,27 +1,37 @@ from abc import ABC, abstractmethod +from aciniformes_backend.models import Fetcher +from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema +from ping.settings import get_settings class CrudServiceInterface(ABC): @abstractmethod - async def get_fetcher(self): + async def get_fetchers(self) -> list[Fetcher]: raise NotImplementedError @abstractmethod - async def get_metric(self): + async def add_metric(self, metric: MetricCreateSchema): raise NotImplementedError - @abstractmethod - async def add_metric(self): + +class CrudService(CrudServiceInterface): + def __init__(self): + self.backend_url: str = get_settings().BACKEND_URL + + async def get_fetchers(self) -> list[Fetcher]: raise NotImplementedError - @abstractmethod - async def add_alert(self): + async def add_metric(self, metric: MetricCreateSchema): raise NotImplementedError -class CrudService(CrudServiceInterface): - pass +class FakeCrudService(CrudServiceInterface): + id_incr = 0 + fetcher_repo: dict[int, Fetcher] = dict() + metric_repo: dict[int, MetricCreateSchema] = dict() + async def get_fetchers(self) -> list[Fetcher]: + return list(self.fetcher_repo.values()) -class FakeCrudService(CrudServiceInterface): - pass + async def add_metric(self, metric: MetricCreateSchema): + self.metric_repo[self.id_incr] = metric diff --git a/ping/service/ping.py b/ping/service/ping.py deleted file mode 100644 index 2f38252..0000000 --- a/ping/service/ping.py +++ /dev/null @@ -1,17 +0,0 @@ -from abc import ABC, abstractmethod -import asyncio -import aioschedule as schedule - - -class PingServiceInterface(ABC): - event_loop: asyncio.BaseEventLoop - - @abstractmethod - async def add_event(self, event): - raise NotImplementedError - - -class PingService(PingServiceInterface): - async def add_event(self, event): - schedule.every(1).second.run(event) - self.event_loop.run_until_complete(schedule.run_pending()) diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py new file mode 100644 index 0000000..54fe70b --- /dev/null +++ b/ping/service/scheduler.py @@ -0,0 +1,68 @@ +from abc import ABC, abstractmethod +from aciniformes_backend.models import Fetcher +from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema +from .crud import CrudServiceInterface +from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler + + +class SchedulerServiceInterface(ABC): + crud_service: CrudServiceInterface + scheduler: BaseScheduler | None + + @abstractmethod + async def add_fetcher(self, fetcher: Fetcher): + raise NotImplementedError + + @abstractmethod + async def delete_fetcher(self): + raise NotImplementedError + + @abstractmethod + async def start(self): + raise NotImplementedError + + @abstractmethod + async def stop(self): + raise NotImplementedError + + async def _add_metric(self, metric: MetricCreateSchema): + await self.crud_service.add_metric(metric) + + +class FakeSchedulerService(SchedulerServiceInterface): + def __init__(self, crud_service: CrudServiceInterface): + self.scheduler = None + self.crud_service = crud_service + + async def add_fetcher(self, fetcher: Fetcher): + pass + + async def delete_fetcher(self): + pass + + async def start(self): + pass + + async def stop(self): + pass + + +class ApSchedulerService(SchedulerServiceInterface): + def __init__(self, crud_service: CrudServiceInterface): + self.scheduler = AsyncIOScheduler() + self.crud_service = crud_service + + async def add_fetcher(self, fetcher: Fetcher): + self.scheduler.add_job(self.make_func(fetcher)) + + async def delete_fetcher(self): + pass + + async def start(self): + pass + + async def stop(self): + pass + + async def make_func(self, fetcher: Fetcher): + pass diff --git a/ping/settings.py b/ping/settings.py index e69de29..a9854e2 100644 --- a/ping/settings.py +++ b/ping/settings.py @@ -0,0 +1,17 @@ +from pydantic import BaseSettings, HttpUrl +from functools import lru_cache + + +class Settings(BaseSettings): + BACKEND_URL: HttpUrl + + class Config: + """Pydantic BaseSettings config""" + + case_sensitive = True + env_file = ".env" + + +@lru_cache() +def get_settings(): + return Settings() diff --git a/requirements.txt b/requirements.txt index 92d9a3f..4b8ff68 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,17 @@ -fastapi -sqlalchemy -pydantic -uvicorn -alembic +fastapi~=0.89.1 +sqlalchemy~=2.0.0 +pydantic~=1.10.4 +uvicorn~=0.20.0 +alembic~=1.9.2 python-dotenv fastapi-sqlalchemy psycopg2-binary gunicorn -starlette -aioschedule -pytest +starlette~=0.22.0 +pytest~=7.2.1 python-jose python-multipart -passlib -bcrypt \ No newline at end of file +passlib~=1.7.4 +bcrypt +APScheduler~=3.10.0 +jose~=1.0.0 \ No newline at end of file diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 24cad30..2c7ef0d 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -3,7 +3,6 @@ from starlette import status from aciniformes_backend.serivce import fetcher_service, Config - def test_fake_service(fake_config): s = fetcher_service() assert s.session is None From ab12c94ab02f6306f95d064caf45a703f7d29e7b Mon Sep 17 00:00:00 2001 From: MikeP Date: Sun, 5 Feb 2023 01:01:42 +0300 Subject: [PATCH 02/53] daily wtf commit ping api finals + partial service impl NOT TESTED --- aciniformes_backend/models/__init__.py | 3 +- ping/__main__.py | 4 +- ping/api/__init__.py | 0 ping/api/asgi.py | 47 +++++++++++++++ ping/asgi.py | 28 --------- ping/service/__init__.py | 7 ++- ping/service/bootstrap.py | 4 +- ping/service/scheduler.py | 57 ++++++++++++++----- tests/backend/api/test_fetcher.py | 1 + tests/ping_service/conftest.py | 0 tests/ping_service/test_api/__init__.py | 0 .../test_api/test_service/__init__.py | 0 12 files changed, 103 insertions(+), 48 deletions(-) create mode 100644 ping/api/__init__.py create mode 100644 ping/api/asgi.py delete mode 100644 ping/asgi.py create mode 100644 tests/ping_service/conftest.py create mode 100644 tests/ping_service/test_api/__init__.py create mode 100644 tests/ping_service/test_api/test_service/__init__.py diff --git a/aciniformes_backend/models/__init__.py b/aciniformes_backend/models/__init__.py index 0708500..5dd4bac 100644 --- a/aciniformes_backend/models/__init__.py +++ b/aciniformes_backend/models/__init__.py @@ -1,7 +1,8 @@ from .metric import Metric from .fetcher import Fetcher from .alerts import Alert, Receiver +from .fetcher import FetcherType from .base import BaseModel from .auth import Auth -__all__ = ["Metric", "Fetcher", "Alert", "Receiver", "BaseModel", "Auth"] +__all__ = ["Metric", "Fetcher", "Alert", "Receiver", "BaseModel", "Auth", "FetcherType"] diff --git a/ping/__main__.py b/ping/__main__.py index ef0a419..11e84df 100644 --- a/ping/__main__.py +++ b/ping/__main__.py @@ -1,6 +1,6 @@ -from .asgi import ping_app +from .api.asgi import ping_app import uvicorn if __name__ == "__main__": - uvicorn.run(ping_app) \ No newline at end of file + uvicorn.run(ping_app, port=8001) diff --git a/ping/api/__init__.py b/ping/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ping/api/asgi.py b/ping/api/asgi.py new file mode 100644 index 0000000..bbaf9ef --- /dev/null +++ b/ping/api/asgi.py @@ -0,0 +1,47 @@ +from ping.service import ( + SchedulerServiceInterface, + scheduler_service, +) +from fastapi import FastAPI, Depends + +ping_app = FastAPI() + + +@ping_app.get( + "/start", +) +async def start_scheduler( + scheduler: SchedulerServiceInterface = Depends(scheduler_service), +): + await scheduler.start() + + +@ping_app.get("/fetchers") +async def get_fetchers( + scheduler: SchedulerServiceInterface = Depends(scheduler_service), +): + return await scheduler.crud_service.get_fetchers() + + +@ping_app.post("/schedule") +async def schedule_all_fetchers_from_db( + scheduler: SchedulerServiceInterface = Depends(scheduler_service), +): + fetchers = await scheduler.crud_service.get_fetchers() + for fetcher in fetchers: + await scheduler.add_fetcher(fetcher) + + +@ping_app.delete("/schedule") +async def delete_all_fetchers_from_scheduler( + scheduler: SchedulerServiceInterface = Depends(scheduler_service), +): + for f in await scheduler.crud_service.get_fetchers(): + await scheduler.delete_fetcher(f) + + +@ping_app.get("/stop") +async def stop_scheduler( + scheduler: SchedulerServiceInterface = Depends(scheduler_service) +): + await scheduler.stop() diff --git a/ping/asgi.py b/ping/asgi.py deleted file mode 100644 index 6186b1e..0000000 --- a/ping/asgi.py +++ /dev/null @@ -1,28 +0,0 @@ -import asyncio -from apscheduler.schedulers.asyncio import AsyncIOScheduler - -from fastapi import FastAPI - -ping_app = FastAPI() - - -def foo(): - print("foo") - - -def bar(): - print("bar") - - -s: AsyncIOScheduler = AsyncIOScheduler() - - -@ping_app.get("/") -async def gg(): - s.add_job(foo, 'interval', seconds=3) - s.start() - - -@ping_app.get("/stop") -async def stop(): - s.shutdown() diff --git a/ping/service/__init__.py b/ping/service/__init__.py index 165842b..09eba5b 100644 --- a/ping/service/__init__.py +++ b/ping/service/__init__.py @@ -1,8 +1,11 @@ from .crud import CrudServiceInterface -from .bootstrap import crud_service +from .scheduler import SchedulerServiceInterface +from .bootstrap import crud_service, scheduler_service __all__ = [ "CrudServiceInterface", + "SchedulerServiceInterface", "crud_service", -] \ No newline at end of file + "scheduler_service", +] diff --git a/ping/service/bootstrap.py b/ping/service/bootstrap.py index 15eef97..30f79f5 100644 --- a/ping/service/bootstrap.py +++ b/ping/service/bootstrap.py @@ -14,5 +14,5 @@ def crud_service(): def scheduler_service(): if Config.fake: - return FakeCrudService() - return ApSchedulerService() \ No newline at end of file + return FakeSchedulerService(crud_service()) + return ApSchedulerService(crud_service()) diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 54fe70b..4d05c9b 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -1,8 +1,11 @@ from abc import ABC, abstractmethod -from aciniformes_backend.models import Fetcher +from aciniformes_backend.models import Fetcher, FetcherType from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from .crud import CrudServiceInterface from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler +import httpx +from datetime import datetime +import time class SchedulerServiceInterface(ABC): @@ -14,7 +17,7 @@ async def add_fetcher(self, fetcher: Fetcher): raise NotImplementedError @abstractmethod - async def delete_fetcher(self): + async def delete_fetcher(self, fetcher: Fetcher): raise NotImplementedError @abstractmethod @@ -32,13 +35,13 @@ async def _add_metric(self, metric: MetricCreateSchema): class FakeSchedulerService(SchedulerServiceInterface): def __init__(self, crud_service: CrudServiceInterface): self.scheduler = None - self.crud_service = crud_service + self.container = crud_service async def add_fetcher(self, fetcher: Fetcher): pass - async def delete_fetcher(self): - pass + async def delete_fetcher(self, fetcher: Fetcher): + self.scheduler.remove_job(fetcher.name) async def start(self): pass @@ -53,16 +56,44 @@ def __init__(self, crud_service: CrudServiceInterface): self.crud_service = crud_service async def add_fetcher(self, fetcher: Fetcher): - self.scheduler.add_job(self.make_func(fetcher)) + f = await self._fetch_it(fetcher) + self.scheduler.add_job( + f, + name=f"{fetcher.name}", + next_run_time=fetcher.delay_ok, + trigger="interval", + ) - async def delete_fetcher(self): - pass + async def delete_fetcher(self, fetcher: Fetcher): + self.scheduler.remove_job(fetcher.name) async def start(self): - pass + self.scheduler.start() async def stop(self): - pass - - async def make_func(self, fetcher: Fetcher): - pass + self.scheduler.shutdown() + + @staticmethod + async def _parse_timedelta(fetcher: Fetcher): + return fetcher.delay_ok, fetcher.delay_fail + + async def _fetch_it(self, fetcher: Fetcher): + prev = time.time() + res = None + match fetcher.type_: + case FetcherType.GET: + res = httpx.get(fetcher.address) + case FetcherType.POST: + res = httpx.post(fetcher.address, data=fetcher.fetch_data) + case FetcherType.PING: + res = httpx.get(fetcher.address) + cur = time.time() + timing = cur - prev + metric = MetricCreateSchema( + metrics={ + "status": res.status_code, + "dt_created": datetime.utcnow(), + "timing": timing, + } + ) + await self.crud_service.add_metric(metric) diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 2c7ef0d..24cad30 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -3,6 +3,7 @@ from starlette import status from aciniformes_backend.serivce import fetcher_service, Config + def test_fake_service(fake_config): s = fetcher_service() assert s.session is None diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/ping_service/test_api/__init__.py b/tests/ping_service/test_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/ping_service/test_api/test_service/__init__.py b/tests/ping_service/test_api/test_service/__init__.py new file mode 100644 index 0000000..e69de29 From 6cf5c64ee82ac8389a4a49fb9932e1db84613456 Mon Sep 17 00:00:00 2001 From: MikeP Date: Wed, 8 Feb 2023 20:41:26 +0300 Subject: [PATCH 03/53] alert pipeline initial not tested) --- alert_bot/__main__.py | 6 ++++++ alert_bot/asgi.py | 16 ++++++++++++---- alert_bot/settings.py | 17 +++++++++++++++++ ping/api/asgi.py | 6 +++--- ping/service/__init__.py | 3 ++- ping/service/crud.py | 21 ++++++++++++++++++--- ping/service/scheduler.py | 19 ++++++++++++++++++- tests/ping_service/conftest.py | 17 +++++++++++++++++ 8 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 alert_bot/settings.py diff --git a/alert_bot/__main__.py b/alert_bot/__main__.py index e69de29..ebaf151 100644 --- a/alert_bot/__main__.py +++ b/alert_bot/__main__.py @@ -0,0 +1,6 @@ +from .asgi import app +import uvicorn + + +if __name__ == "__main__": + uvicorn.run(app, port=8002) diff --git a/alert_bot/asgi.py b/alert_bot/asgi.py index d301dde..3cbf80f 100644 --- a/alert_bot/asgi.py +++ b/alert_bot/asgi.py @@ -1,9 +1,17 @@ -from fastapi import FastAPI +from fastapi import FastAPI, Depends +from aciniformes_backend.models import Alert +from aiogram.bot.bot import Bot +from functools import lru_cache +from alert_bot.settings import get_settings +app = FastAPI() -app = FastAPI +@lru_cache(None) +def get_bot(): + return Bot(get_settings().BOT_TOKEN) @app.post("/alert") -def post_alert(): - pass +async def post_alert(alert: dict, bot: Bot = Depends(get_bot)): + await bot.send_message(alert.receiver, str(alert.data)) + diff --git a/alert_bot/settings.py b/alert_bot/settings.py new file mode 100644 index 0000000..8d56e98 --- /dev/null +++ b/alert_bot/settings.py @@ -0,0 +1,17 @@ +from pydantic import BaseSettings, HttpUrl +from functools import lru_cache + + +class Settings(BaseSettings): + BOT_TOKEN: str + + class Config: + """Pydantic BaseSettings config""" + + case_sensitive = True + env_file = ".env" + + +@lru_cache() +def get_settings(): + return Settings() diff --git a/ping/api/asgi.py b/ping/api/asgi.py index bbaf9ef..52166e7 100644 --- a/ping/api/asgi.py +++ b/ping/api/asgi.py @@ -16,11 +16,11 @@ async def start_scheduler( await scheduler.start() -@ping_app.get("/fetchers") +@ping_app.get("/fetchers_active") async def get_fetchers( scheduler: SchedulerServiceInterface = Depends(scheduler_service), ): - return await scheduler.crud_service.get_fetchers() + return scheduler.scheduler.get_jobs() @ping_app.post("/schedule") @@ -42,6 +42,6 @@ async def delete_all_fetchers_from_scheduler( @ping_app.get("/stop") async def stop_scheduler( - scheduler: SchedulerServiceInterface = Depends(scheduler_service) + scheduler: SchedulerServiceInterface = Depends(scheduler_service), ): await scheduler.stop() diff --git a/ping/service/__init__.py b/ping/service/__init__.py index 09eba5b..e4ffa12 100644 --- a/ping/service/__init__.py +++ b/ping/service/__init__.py @@ -1,6 +1,6 @@ from .crud import CrudServiceInterface from .scheduler import SchedulerServiceInterface -from .bootstrap import crud_service, scheduler_service +from .bootstrap import crud_service, scheduler_service, Config __all__ = [ @@ -8,4 +8,5 @@ "SchedulerServiceInterface", "crud_service", "scheduler_service", + "Config", ] diff --git a/ping/service/crud.py b/ping/service/crud.py index 61ae103..66750a3 100644 --- a/ping/service/crud.py +++ b/ping/service/crud.py @@ -1,5 +1,8 @@ from abc import ABC, abstractmethod -from aciniformes_backend.models import Fetcher + +import httpx + +from aciniformes_backend.models import Fetcher, Alert from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from ping.settings import get_settings @@ -13,21 +16,29 @@ async def get_fetchers(self) -> list[Fetcher]: async def add_metric(self, metric: MetricCreateSchema): raise NotImplementedError + @abstractmethod + async def get_alerts(self) -> list[Alert]: + raise NotImplementedError + class CrudService(CrudServiceInterface): def __init__(self): self.backend_url: str = get_settings().BACKEND_URL async def get_fetchers(self) -> list[Fetcher]: - raise NotImplementedError + return httpx.get(f"{self.backend_url}/fetcher").json() async def add_metric(self, metric: MetricCreateSchema): - raise NotImplementedError + return httpx.post(f"{self.backend_url}/metric", data=metric.dict()) + + async def get_alerts(self) -> list[Alert]: + raise httpx.get(f"{self.backend_url}/alert").json() class FakeCrudService(CrudServiceInterface): id_incr = 0 fetcher_repo: dict[int, Fetcher] = dict() + alert_repo: dict[int, Alert] = dict() metric_repo: dict[int, MetricCreateSchema] = dict() async def get_fetchers(self) -> list[Fetcher]: @@ -35,3 +46,7 @@ async def get_fetchers(self) -> list[Fetcher]: async def add_metric(self, metric: MetricCreateSchema): self.metric_repo[self.id_incr] = metric + self.id_incr += 1 + + async def get_alerts(self) -> list[Alert]: + return list(self.alert_repo.values()) diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 4d05c9b..02b4192 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from aciniformes_backend.models import Fetcher, FetcherType +from aciniformes_backend.models import Fetcher, FetcherType, Alert from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from .crud import CrudServiceInterface from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler @@ -28,9 +28,17 @@ async def start(self): async def stop(self): raise NotImplementedError + @abstractmethod + async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): + raise NotImplementedError + async def _add_metric(self, metric: MetricCreateSchema): await self.crud_service.add_metric(metric) + @property + def alerts(self): + return await self.crud_service.get_alerts() + class FakeSchedulerService(SchedulerServiceInterface): def __init__(self, crud_service: CrudServiceInterface): @@ -49,6 +57,9 @@ async def start(self): async def stop(self): pass + async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): + raise NotImplementedError + class ApSchedulerService(SchedulerServiceInterface): def __init__(self, crud_service: CrudServiceInterface): @@ -73,6 +84,9 @@ async def start(self): async def stop(self): self.scheduler.shutdown() + async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): + raise NotImplementedError + @staticmethod async def _parse_timedelta(fetcher: Fetcher): return fetcher.delay_ok, fetcher.delay_fail @@ -96,4 +110,7 @@ async def _fetch_it(self, fetcher: Fetcher): "timing": timing, } ) + for alert in self.alerts: + if alert.filter == str(res.status_code): + await self.write_alert(metric, alert) await self.crud_service.add_metric(metric) diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index e69de29..a09204c 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -0,0 +1,17 @@ +import pytest +from ping.api.asgi import ping_app +from fastapi.testclient import TestClient +from ping.service import Config + + +@pytest.fixture(scope="session") +def fake_config(): + Config.fake = True + conf = Config() + yield conf + + +@pytest.fixture(scope="session") +def ping_client(fake_config): + client = TestClient(ping_app) + return client From aadfa1db3e5368461d1ef72738f90b22dbdd2535 Mon Sep 17 00:00:00 2001 From: MikeP Date: Fri, 10 Feb 2023 18:16:17 +0300 Subject: [PATCH 04/53] fixes --- aciniformes_backend/models/auth.py | 2 +- aciniformes_backend/routes/auth.py | 4 ++-- aciniformes_backend/routes/fetcher.py | 4 ++-- aciniformes_backend/routes/mectric.py | 2 +- alert_bot/asgi.py | 15 +++++++++++---- migrator/versions/85489ec3d0d0_auth.py | 2 +- ping/service/scheduler.py | 6 +++--- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/aciniformes_backend/models/auth.py b/aciniformes_backend/models/auth.py index 7dadf00..ef06546 100644 --- a/aciniformes_backend/models/auth.py +++ b/aciniformes_backend/models/auth.py @@ -4,7 +4,7 @@ class Auth(BaseModel): - id_: Mapped[int] = mapped_column( + id: Mapped[int] = mapped_column( Integer, primary_key=True, ) diff --git a/aciniformes_backend/routes/auth.py b/aciniformes_backend/routes/auth.py index d0ae1b6..81678ed 100644 --- a/aciniformes_backend/routes/auth.py +++ b/aciniformes_backend/routes/auth.py @@ -37,9 +37,9 @@ def validate_password(cls, password): settings = get_settings() auth_router = APIRouter(tags=["Authentication"]) -oauth2bearer = OAuth2PasswordBearer(tokenUrl="token") +oauth2bearer = OAuth2PasswordBearer(tokenUrl="/auth/token") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token") async def create_token(**kwargs): diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index 6e26166..540bd3e 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -15,7 +15,7 @@ class CreateSchema(BaseModel): type_: str address: str fetch_data: str - metrics: dict[str, int | str | list] + metrics: dict metric_name: str delay_ok: int delay_fail: int @@ -30,7 +30,7 @@ class UpdateSchema(BaseModel): type_: str | None address: HttpUrl | None fetch_data: str | None - metrics: dict[str, int | str | list] | None + metrics: dict | None metric_name: str | None delay_ok: int | None delay_fail: int | None diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index b62f51f..c2cf606 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -11,7 +11,7 @@ class CreateSchema(BaseModel): - metrics: dict[str, int | str | list] + metrics: dict class ResponsePostSchema(CreateSchema): diff --git a/alert_bot/asgi.py b/alert_bot/asgi.py index 3cbf80f..916fad7 100644 --- a/alert_bot/asgi.py +++ b/alert_bot/asgi.py @@ -1,17 +1,24 @@ from fastapi import FastAPI, Depends -from aciniformes_backend.models import Alert from aiogram.bot.bot import Bot from functools import lru_cache from alert_bot.settings import get_settings +from pydantic import BaseModel +from datetime import datetime + app = FastAPI() +class AlertPostSchema(BaseModel): + receiver_id: str + data: str + timestamp: datetime + + @lru_cache(None) def get_bot(): return Bot(get_settings().BOT_TOKEN) @app.post("/alert") -async def post_alert(alert: dict, bot: Bot = Depends(get_bot)): - await bot.send_message(alert.receiver, str(alert.data)) - +async def post_alert(alert: AlertPostSchema, bot: Bot = Depends(get_bot)): + await bot.send_message(alert.receiver_id, str(alert.data) + str(alert.timestamp)) diff --git a/migrator/versions/85489ec3d0d0_auth.py b/migrator/versions/85489ec3d0d0_auth.py index 568f929..659ce25 100644 --- a/migrator/versions/85489ec3d0d0_auth.py +++ b/migrator/versions/85489ec3d0d0_auth.py @@ -31,7 +31,7 @@ def upgrade() -> None: sa.Column("name", sa.String(), nullable=False), sa.Column( "type", - sa.Enum("GET", "POST", "PING", name="fetchertype", native_enum=False), + sa.Enum("GET", "POST", "PING", name="fetcher_type", native_enum=False), nullable=False, ), sa.Column("address", sa.String(), nullable=False), diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 02b4192..2ea0a67 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -36,7 +36,7 @@ async def _add_metric(self, metric: MetricCreateSchema): await self.crud_service.add_metric(metric) @property - def alerts(self): + async def alerts(self): return await self.crud_service.get_alerts() @@ -100,7 +100,7 @@ async def _fetch_it(self, fetcher: Fetcher): case FetcherType.POST: res = httpx.post(fetcher.address, data=fetcher.fetch_data) case FetcherType.PING: - res = httpx.get(fetcher.address) + res = httpx.head(fetcher.address) cur = time.time() timing = cur - prev metric = MetricCreateSchema( @@ -110,7 +110,7 @@ async def _fetch_it(self, fetcher: Fetcher): "timing": timing, } ) - for alert in self.alerts: + for alert in await self.alerts: if alert.filter == str(res.status_code): await self.write_alert(metric, alert) await self.crud_service.add_metric(metric) From 4513377ce20851aa3b94b83015b7f6e1155ebf30 Mon Sep 17 00:00:00 2001 From: MikeP Date: Sun, 12 Feb 2023 17:32:29 +0300 Subject: [PATCH 05/53] add_metric impl done get_fetcher impl done scheduler impl done todo: upd timing on fail, impl for alerts, tg bot as an endpoint --- aciniformes_backend/routes/mectric.py | 2 +- ping/api/asgi.py | 11 ++++++--- ping/service/crud.py | 6 ++--- ping/service/exceptions.py | 3 +++ ping/service/scheduler.py | 34 +++++++++++++++++++++------ ping/settings.py | 2 +- tests/backend/conftest.py | 2 ++ 7 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 ping/service/exceptions.py diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index c2cf606..82416b6 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -30,7 +30,7 @@ async def create( metric_schema: CreateSchema, metric: MetricServiceInterface = Depends(metric_service), ): - id_ = await metric.create(metric_schema.metrics) + id_ = await metric.create(metric_schema.dict()) return ResponsePostSchema(**metric_schema.dict(), id=id_) diff --git a/ping/api/asgi.py b/ping/api/asgi.py index 52166e7..04115a4 100644 --- a/ping/api/asgi.py +++ b/ping/api/asgi.py @@ -2,7 +2,9 @@ SchedulerServiceInterface, scheduler_service, ) -from fastapi import FastAPI, Depends +from fastapi import FastAPI, Depends, HTTPException +from starlette import status +import ping.service.exceptions as exc ping_app = FastAPI() @@ -13,14 +15,17 @@ async def start_scheduler( scheduler: SchedulerServiceInterface = Depends(scheduler_service), ): - await scheduler.start() + try: + await scheduler.start() + except exc.AlreadyRunning as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=e.__repr__()) @ping_app.get("/fetchers_active") async def get_fetchers( scheduler: SchedulerServiceInterface = Depends(scheduler_service), ): - return scheduler.scheduler.get_jobs() + return await scheduler.get_jobs() @ping_app.post("/schedule") diff --git a/ping/service/crud.py b/ping/service/crud.py index 66750a3..ef93e6e 100644 --- a/ping/service/crud.py +++ b/ping/service/crud.py @@ -26,13 +26,13 @@ def __init__(self): self.backend_url: str = get_settings().BACKEND_URL async def get_fetchers(self) -> list[Fetcher]: - return httpx.get(f"{self.backend_url}/fetcher").json() + return [Fetcher(**d) for d in httpx.get(f"{self.backend_url}/fetcher").json()] async def add_metric(self, metric: MetricCreateSchema): - return httpx.post(f"{self.backend_url}/metric", data=metric.dict()) + return httpx.post(f"{self.backend_url}/metric", data=metric.json()) async def get_alerts(self) -> list[Alert]: - raise httpx.get(f"{self.backend_url}/alert").json() + return httpx.get(f"{self.backend_url}/alert").json() class FakeCrudService(CrudServiceInterface): diff --git a/ping/service/exceptions.py b/ping/service/exceptions.py new file mode 100644 index 0000000..bc9b594 --- /dev/null +++ b/ping/service/exceptions.py @@ -0,0 +1,3 @@ +class AlreadyRunning(Exception): + def __init__(self): + super().__init__("Scheduler is already running") diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 2ea0a67..d9004ff 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -2,6 +2,7 @@ from aciniformes_backend.models import Fetcher, FetcherType, Alert from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from .crud import CrudServiceInterface +from .exceptions import AlreadyRunning from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler import httpx from datetime import datetime @@ -20,6 +21,10 @@ async def add_fetcher(self, fetcher: Fetcher): async def delete_fetcher(self, fetcher: Fetcher): raise NotImplementedError + @abstractmethod + async def get_jobs(self): + raise NotImplementedError + @abstractmethod async def start(self): raise NotImplementedError @@ -51,6 +56,9 @@ async def add_fetcher(self, fetcher: Fetcher): async def delete_fetcher(self, fetcher: Fetcher): self.scheduler.remove_job(fetcher.name) + async def get_jobs(self): + raise NotImplementedError + async def start(self): pass @@ -61,24 +69,34 @@ async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): raise NotImplementedError +async def foo(): + print("ddd") + + class ApSchedulerService(SchedulerServiceInterface): + scheduler = AsyncIOScheduler() + def __init__(self, crud_service: CrudServiceInterface): - self.scheduler = AsyncIOScheduler() self.crud_service = crud_service async def add_fetcher(self, fetcher: Fetcher): - f = await self._fetch_it(fetcher) self.scheduler.add_job( - f, - name=f"{fetcher.name}", - next_run_time=fetcher.delay_ok, + self._fetch_it, + args=[fetcher], + id=f"{fetcher.name}", + seconds=fetcher.delay_ok, trigger="interval", ) async def delete_fetcher(self, fetcher: Fetcher): self.scheduler.remove_job(fetcher.name) + async def get_jobs(self): + return [j.id for j in self.scheduler.get_jobs()] + async def start(self): + if self.scheduler.running: + raise AlreadyRunning self.scheduler.start() async def stop(self): @@ -86,6 +104,7 @@ async def stop(self): async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): raise NotImplementedError + # await self.crud_service.add_metric(metric_log) @staticmethod async def _parse_timedelta(fetcher: Fetcher): @@ -106,8 +125,9 @@ async def _fetch_it(self, fetcher: Fetcher): metric = MetricCreateSchema( metrics={ "status": res.status_code, - "dt_created": datetime.utcnow(), - "timing": timing, + "address": fetcher.address, + "data": fetcher.fetch_data, + "timing_ms": timing, } ) for alert in await self.alerts: diff --git a/ping/settings.py b/ping/settings.py index a9854e2..406647f 100644 --- a/ping/settings.py +++ b/ping/settings.py @@ -3,7 +3,7 @@ class Settings(BaseSettings): - BACKEND_URL: HttpUrl + BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" class Config: """Pydantic BaseSettings config""" diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index 36acf56..9ae79a1 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -19,7 +19,9 @@ def engine(): def tables(engine): BaseModel.metadata.create_all(engine) yield + # truncate all tables BaseModel.metadata.drop_all(engine) + BaseModel.metadata.create_all(engine) @pytest.fixture(scope="session") From 61d73e6f39eb7564008c89a595ad6ca8a1a83047 Mon Sep 17 00:00:00 2001 From: MikeP Date: Sun, 12 Feb 2023 23:11:45 +0300 Subject: [PATCH 06/53] change delay on alert impl --- aciniformes_backend/routes/auth.py | 1 - ping/service/scheduler.py | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/aciniformes_backend/routes/auth.py b/aciniformes_backend/routes/auth.py index 81678ed..c91c6dc 100644 --- a/aciniformes_backend/routes/auth.py +++ b/aciniformes_backend/routes/auth.py @@ -21,7 +21,6 @@ class Token(BaseModel): class User(BaseModel): id: str username: str - email: str | None = None class RegistrationForm(BaseModel): diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index d9004ff..b9845bc 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -5,7 +5,7 @@ from .exceptions import AlreadyRunning from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler import httpx -from datetime import datetime +from datetime import datetime, timedelta import time @@ -83,7 +83,7 @@ async def add_fetcher(self, fetcher: Fetcher): self.scheduler.add_job( self._fetch_it, args=[fetcher], - id=f"{fetcher.name}", + id=f"{fetcher.name} {fetcher.create_ts}", seconds=fetcher.delay_ok, trigger="interval", ) @@ -124,13 +124,23 @@ async def _fetch_it(self, fetcher: Fetcher): timing = cur - prev metric = MetricCreateSchema( metrics={ - "status": res.status_code, - "address": fetcher.address, - "data": fetcher.fetch_data, + "status_code": res.status_code, + "url": fetcher.address, + "body": fetcher.fetch_data, "timing_ms": timing, } ) + self.scheduler.reschedule_job( + f"{fetcher.name} {fetcher.create_ts}", + seconds=fetcher.delay_ok, + trigger="interval", + ) for alert in await self.alerts: if alert.filter == str(res.status_code): + self.scheduler.reschedule_job( + f"{fetcher.name} {fetcher.create_ts}", + seconds=fetcher.delay_fail, + trigger="interval", + ) await self.write_alert(metric, alert) await self.crud_service.add_metric(metric) From e232430462b560b44c8b3790c0b15a76843b9b33 Mon Sep 17 00:00:00 2001 From: MikeP Date: Mon, 13 Feb 2023 11:20:01 +0300 Subject: [PATCH 07/53] admin creds in dotenv --- aciniformes_backend/routes/alert/alert.py | 4 ++-- aciniformes_backend/serivce/auth.py | 5 +++++ aciniformes_backend/settings.py | 1 + alert_bot/settings.py | 1 + ping/service/scheduler.py | 2 -- ping/settings.py | 1 + 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index b8c1474..0c907c7 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -11,7 +11,7 @@ class CreateSchema(BaseModel): - data: dict[str, int | str | list] + data: dict receiver: int filter: str @@ -21,7 +21,7 @@ class PostResponseSchema(CreateSchema): class UpdateSchema(BaseModel): - data: dict[str, int | str | list] | None + data: dict | None receiver: int | None filter: str | None diff --git a/aciniformes_backend/serivce/auth.py b/aciniformes_backend/serivce/auth.py index 1732b62..ab1000f 100644 --- a/aciniformes_backend/serivce/auth.py +++ b/aciniformes_backend/serivce/auth.py @@ -20,6 +20,11 @@ async def registrate_user(self, username, password) -> db_models.Auth | None: return self.session.scalar(q) async def authenticate_user(self, username, password) -> db_models.Auth | None: + password_from_settings = settings.ADMIN_SECRET.get(username) + if password_from_settings and await self._validate_password( + settings.PWD_CONTEXT.hash(password_from_settings), password + ): + return db_models.Auth(username=username, password=password) db_user = await self.get_user(username) if not db_user: raise exc.NotRegistered(username) diff --git a/aciniformes_backend/settings.py b/aciniformes_backend/settings.py index b6b34b5..73b05d6 100644 --- a/aciniformes_backend/settings.py +++ b/aciniformes_backend/settings.py @@ -8,6 +8,7 @@ class Settings(BaseSettings): DB_DSN: PostgresDsn PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto") EXPIRY_TIMEDELTA: datetime.timedelta = datetime.timedelta(days=7) + ADMIN_SECRET: dict[str, str] = {"admin": "42"} JWT_KEY = "42" ALGORITHM: str = "HS256" diff --git a/alert_bot/settings.py b/alert_bot/settings.py index 8d56e98..6c58e74 100644 --- a/alert_bot/settings.py +++ b/alert_bot/settings.py @@ -4,6 +4,7 @@ class Settings(BaseSettings): BOT_TOKEN: str + PING_URL: HttpUrl = "http://127.0.0.1:8001" class Config: """Pydantic BaseSettings config""" diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index b9845bc..a3f8dd3 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -5,7 +5,6 @@ from .exceptions import AlreadyRunning from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler import httpx -from datetime import datetime, timedelta import time @@ -104,7 +103,6 @@ async def stop(self): async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): raise NotImplementedError - # await self.crud_service.add_metric(metric_log) @staticmethod async def _parse_timedelta(fetcher: Fetcher): diff --git a/ping/settings.py b/ping/settings.py index 406647f..2990724 100644 --- a/ping/settings.py +++ b/ping/settings.py @@ -4,6 +4,7 @@ class Settings(BaseSettings): BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" + BOT_URL: HttpUrl = "http://127.0.0.1:8002" class Config: """Pydantic BaseSettings config""" From 30425612225d62ae3a035da27a510eb016d60a62 Mon Sep 17 00:00:00 2001 From: MikeP Date: Mon, 13 Feb 2023 18:38:14 +0300 Subject: [PATCH 08/53] ping tests initial --- aciniformes_backend/serivce/auth.py | 1 + ping/service/scheduler.py | 29 +++++++------ tests/backend/service/test_fetcher_service.py | 1 - .../{test_api => api}/__init__.py | 0 .../__init__.py => api/conftest.py} | 0 tests/ping_service/api/test_asgi.py | 27 ++++++++++++ tests/ping_service/conftest.py | 7 ++++ tests/ping_service/service/__init__.py | 0 tests/ping_service/service/conftest.py | 0 tests/ping_service/service/test_scheduler.py | 42 +++++++++++++++++++ 10 files changed, 93 insertions(+), 14 deletions(-) rename tests/ping_service/{test_api => api}/__init__.py (100%) rename tests/ping_service/{test_api/test_service/__init__.py => api/conftest.py} (100%) create mode 100644 tests/ping_service/api/test_asgi.py create mode 100644 tests/ping_service/service/__init__.py create mode 100644 tests/ping_service/service/conftest.py create mode 100644 tests/ping_service/service/test_scheduler.py diff --git a/aciniformes_backend/serivce/auth.py b/aciniformes_backend/serivce/auth.py index ab1000f..6f231ed 100644 --- a/aciniformes_backend/serivce/auth.py +++ b/aciniformes_backend/serivce/auth.py @@ -20,6 +20,7 @@ async def registrate_user(self, username, password) -> db_models.Auth | None: return self.session.scalar(q) async def authenticate_user(self, username, password) -> db_models.Auth | None: + # first, compare with dotenv creds password_from_settings = settings.ADMIN_SECRET.get(username) if password_from_settings and await self._validate_password( settings.PWD_CONTEXT.hash(password_from_settings), password diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index a3f8dd3..48080d3 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -5,12 +5,16 @@ from .exceptions import AlreadyRunning from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler import httpx +from datetime import datetime, timedelta +from ping.settings import get_settings import time +settings = get_settings() + class SchedulerServiceInterface(ABC): crud_service: CrudServiceInterface - scheduler: BaseScheduler | None + scheduler: BaseScheduler | dict @abstractmethod async def add_fetcher(self, fetcher: Fetcher): @@ -45,31 +49,30 @@ async def alerts(self): class FakeSchedulerService(SchedulerServiceInterface): + scheduler = dict() + def __init__(self, crud_service: CrudServiceInterface): - self.scheduler = None - self.container = crud_service + self.crud_service = crud_service async def add_fetcher(self, fetcher: Fetcher): - pass + self.scheduler[fetcher.id_] = fetcher async def delete_fetcher(self, fetcher: Fetcher): - self.scheduler.remove_job(fetcher.name) + del self.scheduler[fetcher.name] async def get_jobs(self): raise NotImplementedError async def start(self): - pass + if 'started' in self.scheduler: + raise AlreadyRunning + self.scheduler['started'] = True async def stop(self): - pass + self.scheduler['started'] = False async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - raise NotImplementedError - - -async def foo(): - print("ddd") + httpx.post(f"{settings.BOT_URL}/alert", data=metric_log.json()) class ApSchedulerService(SchedulerServiceInterface): @@ -102,7 +105,7 @@ async def stop(self): self.scheduler.shutdown() async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - raise NotImplementedError + httpx.post(f"{settings.BOT_URL}/alert", data=metric_log.json()) @staticmethod async def _parse_timedelta(fetcher: Fetcher): diff --git a/tests/backend/service/test_fetcher_service.py b/tests/backend/service/test_fetcher_service.py index 86e7806..c7be91b 100644 --- a/tests/backend/service/test_fetcher_service.py +++ b/tests/backend/service/test_fetcher_service.py @@ -3,7 +3,6 @@ from aciniformes_backend.routes.fetcher import CreateSchema as FetcherCreateSchema from aciniformes_backend.models import Fetcher -import aciniformes_backend.serivce.exceptions as exc @pytest.fixture diff --git a/tests/ping_service/test_api/__init__.py b/tests/ping_service/api/__init__.py similarity index 100% rename from tests/ping_service/test_api/__init__.py rename to tests/ping_service/api/__init__.py diff --git a/tests/ping_service/test_api/test_service/__init__.py b/tests/ping_service/api/conftest.py similarity index 100% rename from tests/ping_service/test_api/test_service/__init__.py rename to tests/ping_service/api/conftest.py diff --git a/tests/ping_service/api/test_asgi.py b/tests/ping_service/api/test_asgi.py new file mode 100644 index 0000000..212c77b --- /dev/null +++ b/tests/ping_service/api/test_asgi.py @@ -0,0 +1,27 @@ +from starlette import status + + +class TestAsgi: + def test_schedule_success(self, ping_client, crud_client): + res = ping_client.post('/schedule') + assert res.status_code == status.HTTP_200_OK + assert res.json() is None + + def test_start(self, ping_client): + res = ping_client.get('/start') + assert res.status_code == status.HTTP_200_OK + ping_client.get('/stop') + + def test_start_already_started(self, ping_client): + ping_client.get('/start') + res2 = ping_client.get('/start') + assert res2.status_code == status.HTTP_409_CONFLICT + + def test_stop(self, ping_client): + pass + + def test_get_active_jobs(self, ping_client): + pass + + def test_delete_schedulers(self, ping_client): + pass diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index a09204c..c9f3a23 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -2,6 +2,7 @@ from ping.api.asgi import ping_app from fastapi.testclient import TestClient from ping.service import Config +from aciniformes_backend.routes import app @pytest.fixture(scope="session") @@ -11,6 +12,12 @@ def fake_config(): yield conf +@pytest.fixture(scope="session") +def crud_client(): + client = TestClient(app) + return client + + @pytest.fixture(scope="session") def ping_client(fake_config): client = TestClient(ping_app) diff --git a/tests/ping_service/service/__init__.py b/tests/ping_service/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py new file mode 100644 index 0000000..395dc3f --- /dev/null +++ b/tests/ping_service/service/test_scheduler.py @@ -0,0 +1,42 @@ +import pytest +from ping.service import Config + + +@pytest.fixture +def pg_config(): + Config.fake = False + yield Config() + + +class TestSchedulerService: + @pytest.mark.asyncio + async def test_add_fetcher_success(self): + pass + + @pytest.mark.asyncio + async def test_delete_fetcher(self): + pass + + @pytest.mark.asyncio + async def test_get_jobs(self): + pass + + @pytest.mark.asyncio + async def test_start_success(self): + pass + + @pytest.mark.asyncio + async def test_start_alrady_started(self): + pass + + @pytest.mark.asyncio + async def test_stop(self): + pass + + @pytest.mark.asyncio + async def test_stop_already_stopped(self): + pass + + @pytest.mark.asyncio + async def test_write_alert(self): + pass From 76593edf6e6a1b366d2a6139c149045e6dbd98b7 Mon Sep 17 00:00:00 2001 From: MikeP Date: Mon, 13 Feb 2023 19:54:35 +0300 Subject: [PATCH 09/53] ping api tests complete --- ping/service/crud.py | 13 ++++++++++++- ping/service/scheduler.py | 10 +++++----- tests/ping_service/api/test_asgi.py | 20 ++++++++++++-------- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/ping/service/crud.py b/ping/service/crud.py index ef93e6e..befdd2c 100644 --- a/ping/service/crud.py +++ b/ping/service/crud.py @@ -37,7 +37,18 @@ async def get_alerts(self) -> list[Alert]: class FakeCrudService(CrudServiceInterface): id_incr = 0 - fetcher_repo: dict[int, Fetcher] = dict() + fetcher_repo: dict[int, Fetcher] = { + 0: Fetcher(**{ + "name": "https://www.python.org", + "type_": "get_ok", + "address": "https://www.python.org", + "fetch_data": "string", + "metrics": {}, + "metric_name": "string", + "delay_ok": 30, + "delay_fail": 40, + }) + } alert_repo: dict[int, Alert] = dict() metric_repo: dict[int, MetricCreateSchema] = dict() diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 48080d3..5824d78 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -58,18 +58,18 @@ async def add_fetcher(self, fetcher: Fetcher): self.scheduler[fetcher.id_] = fetcher async def delete_fetcher(self, fetcher: Fetcher): - del self.scheduler[fetcher.name] + del self.scheduler[fetcher.id_] async def get_jobs(self): - raise NotImplementedError + return await self.crud_service.get_fetchers() async def start(self): - if 'started' in self.scheduler: + if "started" in self.scheduler: raise AlreadyRunning - self.scheduler['started'] = True + self.scheduler["started"] = True async def stop(self): - self.scheduler['started'] = False + self.scheduler["started"] = False async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): httpx.post(f"{settings.BOT_URL}/alert", data=metric_log.json()) diff --git a/tests/ping_service/api/test_asgi.py b/tests/ping_service/api/test_asgi.py index 212c77b..4d3cf66 100644 --- a/tests/ping_service/api/test_asgi.py +++ b/tests/ping_service/api/test_asgi.py @@ -3,25 +3,29 @@ class TestAsgi: def test_schedule_success(self, ping_client, crud_client): - res = ping_client.post('/schedule') + res = ping_client.post("/schedule") assert res.status_code == status.HTTP_200_OK assert res.json() is None def test_start(self, ping_client): - res = ping_client.get('/start') + res = ping_client.get("/start") assert res.status_code == status.HTTP_200_OK - ping_client.get('/stop') + ping_client.get("/stop") def test_start_already_started(self, ping_client): - ping_client.get('/start') - res2 = ping_client.get('/start') + ping_client.get("/start") + res2 = ping_client.get("/start") assert res2.status_code == status.HTTP_409_CONFLICT def test_stop(self, ping_client): - pass + res = ping_client.get("/stop") + assert res.status_code == status.HTTP_200_OK def test_get_active_jobs(self, ping_client): - pass + res = ping_client.get('/fetchers_active') + assert res.status_code == status.HTTP_200_OK + assert len(res.json()) > 0 def test_delete_schedulers(self, ping_client): - pass + res = ping_client.delete('/schedule') + assert res.status_code == status.HTTP_200_OK From 91a2de1d377ea0ab563fddfb530335ec8d758f21 Mon Sep 17 00:00:00 2001 From: MikeP Date: Mon, 13 Feb 2023 20:31:12 +0300 Subject: [PATCH 10/53] scheduler service tests initial --- tests/ping_service/service/conftest.py | 23 +++++++++ tests/ping_service/service/test_scheduler.py | 51 ++++++++++++-------- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index e69de29..1cb876a 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -0,0 +1,23 @@ +from ping.service import Config, scheduler_service, crud_service +import pytest + + +@pytest.fixture +def pg_config(): + Config.fake = False + yield Config() + + +@pytest.fixture +def pg_scheduler_service(pg_config): + s = scheduler_service() + assert s.scheduler is not dict + yield s + + +@pytest.fixture +def fake_crud_service(pg_config): + Config.fake = True + s = crud_service() + yield s + Config.fake = False diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 395dc3f..fc99035 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,42 +1,53 @@ import pytest -from ping.service import Config - - -@pytest.fixture -def pg_config(): - Config.fake = False - yield Config() - +from aciniformes_backend.models import Fetcher +import ping.service.exceptions as exc + + +@pytest.fixture() +def fetcher_obj(): + yield Fetcher(**{ + "name": "https://www.python.org", + "type_": "get_ok", + "address": "https://www.python.org", + "fetch_data": "string", + "metrics": {}, + "metric_name": "string", + "delay_ok": 30, + "delay_fail": 40, + }) class TestSchedulerService: @pytest.mark.asyncio - async def test_add_fetcher_success(self): - pass + async def test_add_fetcher_success(self, pg_scheduler_service, fake_crud_service, fetcher_obj): + pg_scheduler_service.add_fetcher(fetcher_obj) @pytest.mark.asyncio - async def test_delete_fetcher(self): - pass + async def test_delete_fetcher(self, pg_scheduler_service, fake_crud_service, fetcher_obj): + pg_scheduler_service.delete_fetcher(f"{fetcher_obj.name} {fetcher_obj.create_ts}") @pytest.mark.asyncio - async def test_get_jobs(self): - pass + async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): + res = await pg_scheduler_service.get_jobs() + assert type(res) is list @pytest.mark.asyncio - async def test_start_success(self): - pass + async def test_start_success(self, pg_scheduler_service, fake_crud_service): + await pg_scheduler_service.start() + assert pg_scheduler_service.scheduler.running + await pg_scheduler_service.stop() @pytest.mark.asyncio - async def test_start_alrady_started(self): + async def test_start_already_started(self, pg_scheduler_service, fake_crud_service): pass @pytest.mark.asyncio - async def test_stop(self): + async def test_stop(self, pg_scheduler_service, fake_crud_service): pass @pytest.mark.asyncio - async def test_stop_already_stopped(self): + async def test_stop_already_stopped(self, pg_scheduler_service, fake_crud_service): pass @pytest.mark.asyncio - async def test_write_alert(self): + async def test_write_alert(self, pg_scheduler_service, fake_crud_service): pass From fe484f5f811cefdd651f9ed4cd8abfdf64f1d938 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 10 Apr 2023 13:36:51 +0300 Subject: [PATCH 11/53] Implimenting authlib --- .../__pycache__/__init__.cpython-310.pyc | Bin 170 -> 190 bytes aciniformes_backend/models/__init__.py | 8 +- aciniformes_backend/models/alerts.py | 4 +- aciniformes_backend/models/auth.py | 15 -- aciniformes_backend/models/base.py | 3 +- aciniformes_backend/models/fetcher.py | 4 +- aciniformes_backend/models/metric.py | 6 +- .../__pycache__/__init__.cpython-310.pyc | Bin 209 -> 229 bytes .../routes/__pycache__/base.cpython-310.pyc | Bin 538 -> 871 bytes .../__pycache__/fetcher.cpython-310.pyc | Bin 1326 -> 3007 bytes .../__pycache__/mectric.cpython-310.pyc | Bin 1326 -> 1849 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 183 -> 203 bytes .../alert/__pycache__/alert.cpython-310.pyc | Bin 1330 -> 2702 bytes .../__pycache__/reciever.cpython-310.pyc | Bin 1333 -> 2658 bytes aciniformes_backend/routes/alert/alert.py | 20 +-- aciniformes_backend/routes/alert/reciever.py | 25 ++-- aciniformes_backend/routes/auth.py | 128 ------------------ aciniformes_backend/routes/base.py | 17 ++- aciniformes_backend/routes/fetcher.py | 33 ++--- aciniformes_backend/routes/mectric.py | 14 +- aciniformes_backend/serivce/__init__.py | 24 +--- aciniformes_backend/serivce/alert.py | 5 +- aciniformes_backend/serivce/auth.py | 43 ------ aciniformes_backend/serivce/base.py | 42 +++--- aciniformes_backend/serivce/bootstrap.py | 22 +-- aciniformes_backend/serivce/fake.py | 40 +----- aciniformes_backend/serivce/fetcher.py | 5 +- aciniformes_backend/serivce/metric.py | 3 +- aciniformes_backend/serivce/receiver.py | 5 +- aciniformes_backend/settings.py | 5 +- alert_bot/__main__.py | 2 +- alert_bot/asgi.py | 10 +- alert_bot/settings.py | 3 +- migrator/env.py | 8 +- migrator/versions/85489ec3d0d0_auth.py | 3 +- ping/__main__.py | 2 +- ping/api/asgi.py | 8 +- ping/service/__init__.py | 3 +- ping/service/crud.py | 27 ++-- ping/service/scheduler.py | 18 ++- ping/settings.py | 3 +- tests/backend/api/conftest.py | 3 +- tests/backend/api/test_alert.py | 4 +- tests/backend/api/test_auth.py | 5 +- tests/backend/api/test_fetcher.py | 4 +- tests/backend/api/test_metric.py | 4 +- tests/backend/conftest.py | 7 +- tests/backend/service/conftest.py | 11 +- tests/backend/service/test_alert_serivce.py | 11 +- tests/backend/service/test_fetcher_service.py | 3 +- tests/backend/service/test_metric_service.py | 5 +- tests/ping_service/api/test_asgi.py | 4 +- tests/ping_service/conftest.py | 5 +- tests/ping_service/service/conftest.py | 3 +- tests/ping_service/service/test_scheduler.py | 38 ++++-- 55 files changed, 223 insertions(+), 442 deletions(-) delete mode 100644 aciniformes_backend/models/auth.py delete mode 100644 aciniformes_backend/routes/auth.py delete mode 100644 aciniformes_backend/serivce/auth.py diff --git a/aciniformes_backend/__pycache__/__init__.cpython-310.pyc b/aciniformes_backend/__pycache__/__init__.cpython-310.pyc index ca02b2fa5a81c0f57024913cd23c2b82b22c164f..42cbe5111d8b5cb905e83b5617a4bd5057b02c87 100644 GIT binary patch delta 112 zcmZ3*xQ~%1pO=@50SJ_@1pO=@50SJB5GKjWI6y$)&le zc_qa$?wKVXrAaXbMfqvT`MEKP$(ebXY57IDsl~cLv8>eO5@fOXq{QUx)V!3KqWsd5 K)Z&T9R6 z!pO#-z@N~rSz+lPz{nlXPEvw&e){x1`RV&4Wt~pTqBi;U7`AJc^|PF;H-{$A=`%kl zfCWrh3FC}eU_(vWNsZT+xIx%iVwbSD#7)92!WPt3JMp<+;fCrY0S}mk?Aqd8?4EC6 z9UO3{47|@Z;!DPXCba%zJZOXeAJc*k1Z&JD`WReW=RN4c25f$@p;y*}trM&N;Er5a zeKvOwgei7k9p16U+|k*rKuwvg;ob=vBeQfQ0TjL$8j1F&SVRVkLZ(x5IoN;d}@W0V)Dx%YaBuDrnni}($3?!f|Fxq}1?Eo1JCs_~S!cF#_a z_m4-T!_&UaJ9(C=h)PE}ZOgmTWJ(kyEmC-aidHxRfgC+U7Np=RA;UtY(0#f`I^tlKUIs&Tuap zo*aF%bd&z6!f!!n|4DdeP@C}P>+PqT@3$YrVHVF4ObZjfkj2Z{ILvi6iL)eJQ}T$o zb8@oIt<;6pA(?P24DRHYbMI-A!A#+^^68ju3clsDf8D9|*rL~Mn>oy5tv`POrn~^AU2Sgql$W91OXDA5fWnQQl<6<-2Aa+Cl&2b24;q;0}^k* zYw!}FtPHHoOt|Ti@6(;`cf;N3ew;VEFT&7AMql@@^aCUGy@9rD1D7WBa@D^W0)9rL zE_9~>*vw|TFBV1(Im~I1$J`eA%-a&{+@i=|{T`hx91>@s$_UqvRFLq;MTaP$9A=;v z9!a3252kWS4yEGsIttgH_{!q2KD17s>YZ51QI8iis~C)xs7g@pNV(?FCjH%zI}J(` zHjh&=Q=AMzr=^f2ro7;ZP&w#hsl*g$Ne1oicoNeT3Pzf#noMF1139m~vs|z$gL8}y T%sckYssIIeYsc#0jrZ_B;M!a@ diff --git a/aciniformes_backend/routes/__pycache__/fetcher.cpython-310.pyc b/aciniformes_backend/routes/__pycache__/fetcher.cpython-310.pyc index c0c86b3bcd93d0569e457ad7f801e516bc7a70ce..b2afdc268bdd240522a32fb3c3bec9b990c18f68 100644 GIT binary patch literal 3007 zcmZ`*-EP!I6rLG-?fuUt8Tp)UZpbKBnZ0eV+L&l!7{4Fp?rX6Bq3&zbLhXU3gMrAXju{e6c2 zSRmvt>9!S?ql#0Bs!lZ;acWv;hojD@7~LHcNA{>QF6a(%Caw^k=Y>av7es!?a3-N& z>IOuPLG*`G4p&d zMz48nuqKW_E~fPyi_h}~J!b*tEWS2p@xYwa&lCI|J${kfE2RGJKNzbzQ-upRuHNkQ z5+Rj+NpwY<$91YI%d4w5zWv1)-6ZI=L1D#-m-J$saM6p!wGJ1dvX_&jyCy@h7uMP^ z@Io)SRX5bsWs&%|gj^BwPT-5HZTNc2^Mx8|r3N=nEvhK`Ys3v7JsN%t=$wZVH$VvD zP);1SMuZ_uPS2$^%W5O5O{jBV&t-iJY76>yR_D(FCt&|YRsKc_FA*!?Xyhrg?L|W6 zlC7?AmF;mZMI5W5_SfZJ;wd{4i46Q$mC}Z*XXF6~Z_Dl6R>ic{@`A8Yw^iQN16)@X zT{r6RUMQelcHN(PUWiknXT*tABd+VU+Z})x0E|Iv^1x4&5ww%1M9#t1wfL7q>zl8d zYXDu`e75)e;j=%UKWJX+_`OK9lel>$NS1r+&93aU{7%&L{Gc7QIx-URNziQyKN;4! z>z;obg58uD`?#3}W~sX+%dpTWlz0q8k>u&{Tc+Cn_E^opq~Ta`dXWJB5GPIncSDWc z6mb`JDsFV*Bt^fWSiqm=lPIQ8%%V7oVt_*0I)=LAAW$EhAc~|$wdU=PwPD!d zjtL`k5=oTH+DN3n9?4{7dL&d|kEF6QJrHZqE@YbplC5=tJQ>LvvI0KHDvA*lH53T7 zM5yHv6yqT3`PZSCMw5oZ#ukE{!gClhi(p-Vk_E6#hhM-6?Qh2k@gK%1O*YPS@CsK% z7Be{y;~H`SBD_1%4cZ$*F7-L+>%>%*w07e( zL+#A$Fz>0lOwjtIgN$4iqnxO>W#7N9d$c)GIA^jO{ zjmA@|O6iT@`Vhmqr3&cq`j#7TWx%1Xs@K=?%xQFz%bi}EON^AnP^mGzTimm!&$^B4 ztM29NYmG~FBMkzAEI&c-2WU)G0Y*yj82%hIY4Y7I?zNM^mzZD@PeE1l z;CI6fgY~8UhcTXNcIfKsUvo>4x4|7>NcdAF5`SD}2?a(%*#tu?JoZ5Hb z3dg(zSKh%_PKaY~oS50rCTgjK#7aA!`F1=z^UZA0?fQb&&)1*QZ&HZwY%DK_j9uE* zR}vsVj>SaAQnGH1t%)7mQtS(`!8sA&X#3cX9kN^ClI2Jx245w-w}h|5hQWKV zITZb^A5??r%ck?_`O_E0wARSRV^!&A1?X|g$IFWdF~y$-q-YUK5U~Z4LRn3)hg#Ko zn7-B%HJI6rmn6BG=p-XIxOB{7Cq6` zVO^AwO0zs06*$q=gA$83I<2GaWi)xI(nFm?gj_=vjdV>};C6Y0Eeg;3l>6ohM;heH z!jRp#n1d1)#h^>*BGb9H!dRC?hZQEoSeHWEbh1mk+9Ywt*@4iuc0lgX&=7le)7jVc zoVv!!V9;-2o7~Z3!51`kme)-$NmQN}wW_lsuae}iIjxdA=T&hnz6c~T(uhl$aX0s9 z8*V@h#Y_$^Ml|i=QX4ll{axoDEE0}|8}zv{Dz4EfvisFB+plz;sPXtFdoe$_MdE!c z`jEj#?2(>wl4pzcv!pMCpGo7sbd^ckrVH-SU-RI6&A$f38~oe(bmE+YsTqrV=MiuH zcSL$-y4Ls4Bi<%$&<~sTNL95eGi0t~uRH>k*I9}-d-6n^X`8p!HFJ-RQx;Wix`KvL zCKE#vO{-M&B#_fZzG11bMTfne`73?M99MTq1T;ON9Xg@oI-c!I&*2^Z+3WH**a7U@ diff --git a/aciniformes_backend/routes/__pycache__/mectric.cpython-310.pyc b/aciniformes_backend/routes/__pycache__/mectric.cpython-310.pyc index 4775410910cdf19db9dfe5f87b1abef95516fa4d..8580640bd230132157829d514c39f31328665be9 100644 GIT binary patch literal 1849 zcmZ`)OK;mo5Z+x<)Weia%kndQp#=&MXw?7>dMJV-4>z_Ck%QPq0YU&lb5{;sKGY?Z z)~*A@ajxkf=*0$d$*upPe;~Q;u|4%1^irT{XO@yJ6wL}dn8$Kw=9}43)@(Kite=1T zod0DK@;eIC%L3sB?A)S+aKdR!66(=}d5ogWVk0p_?19PKeEw-rKyNjS3-EbxjOK%5#^h=Ely> zy{~@=#juF76ckoo1jPtV^;e-}c|`!CO5J$d=dtCRj(7LF2;7I}X?DmF%Y{h`bTVV3lRFiN9ACKHi=3c3Rk z7SlR^F9^Q{Wcw0<&ie`20jTcqP}<(YRy|=$yednG%TFnA^;5oXsCv! zeP855NJ!kv@}dN&r&z>aRAdXjm9t13Aj*i+Lbg%7$h+h^s6+hcVCQ9E4%M2s1}10V zEL5hxF2-Kv5@>pI8R-8yz)TjtqMDBTybTh_Q6>cXq$q@~IZ zuu3t+?wKr%y8`=H*!fi;C-j(}kz;a3&&fI6gcrU+9vKDWEE;Q{Q%;YKh(4!3X)t>_ z2OT}6W|h7rqN2fYP5CbJ?*UQf2zmuD<^EK*8p-#;{0{6K=Ma0`u~w->T?o_YVlm)w z>uB*ah?bm@b25$-Vu$k59w1cR7lj|hvCB%&1|ardpC7^vAHyE|Y%!Bc)E!km`!V8= zbY3C)n=C}G23!080+KB3>kK*0!S z%3;OVQ@)*il)URK=6Mk$wADT$?0Z5%uPwX4_s-qwzPXKS~& z<{D)Y5M=oglFM#IoRk5#e*#R7?adxD)~2S>dSg@ek90xRb5~S#5I{)7NMgzob5-rJ zs(U$%bRen@CuJ-Om|i-j+VGGEX%U4I>!+LmhMgZrd)>*MRa9S@8Ud5%-i3~c9)?09 zu(FN>|EMGuMn7}iMN+rErALEQ<`AB(ZVw&2E}C4y4=&TzoX)*=y&Q30VJqc`f&(L@ g{B@{Pbxg;!Oxv&wo0@+!fP^voO|8C6Yn2b^zdc#0ApigX literal 1326 zcmbtT&2G~`5Z?7ViIXOcp?~~H2#HH`pnic06^c}GAVNgF_+l-dEj4v)Gj@;);?%wa zS2*S+xbhCZazY$?f0IIdXJc`>WbDu` zzmfm}G8JPPO3AvDI%5?oDfR>?a8CueMxCh8CA$GlvYXa!klg~0Y|q+FBTq!wGOf=Z zw7*E`z|yHWkzt#VjzzlAv&fP~mdU4Yx(5#UaO2b^&ko@|nBzBL#p26^_ZRS0ShIK^ z)<v5BI(6Gh`3CMoOPD0(~5>Fi`GklD84*C^O84VD2M>!bV~Cc#rv z9#we}=s3xegB-`Ed{|)q#>7>ywTMQqbbMqo2#{+igE4tv5^oj9*r4#d&$w@%aHU1A zEezR>i`g$=UJNz~oo71JmKf`iXtTnESnEn?MJGG7%XJdxob3yvj0D_tddRz}fZb6O^M&a32FI-)C)ky>2IjJvr@ z+j0Y9B&KqBIihV3ms&St+uv2b;E-?~T%*smQE`P%k=?J4*?MiNNT=y-_F{f;gT(tr z@F9VZ*e5;aB+nP?=Sg1*Kb6*f;VGN6q6hBLtyyq>=3fKiExvX!ojBuQV#ngaMZ`P* z9g%)BQ<=?+h<8aF4*a@xpvy`Z2{PBAR~&=Rsw76mo;=ZZ+Gee7+PTNtE%LImT|vtz zlZifwx>4vVK15Cz`GtkT<{kF7XMgD<=D561qD#}`E8q3qrt7JW^jzNlpS>!71MGzC A;Q#;t diff --git a/aciniformes_backend/routes/alert/__pycache__/__init__.cpython-310.pyc b/aciniformes_backend/routes/alert/__pycache__/__init__.cpython-310.pyc index 250dc046619381fae3e3a13b128e84cb825b463a..d31a21fd71770d99acc8ca52190e40e21cd67622 100644 GIT binary patch delta 125 zcmdnac$$$XpO=@50SJr7|%)zpN-z-zBv;yClCrKQTEo xFEcH_C^xlOx1cCLD>b=9Uk_O{J}EIdJ2fvwzbL=7B(+#SF(xegmA)XNIKM^6nPf1j^=2J*FwExIELbNSYtY-;*BuZu^dbB zW|;33oPy$W;Y6qC6gwrS)G0fYl-wcQ;`tYZ=Y_GYIX27-d;;bZ!iM=2%!|AP^ODk6 zU|!~vFrQTOX+gJ%Qx(<^Xn|KA(rx&)7@rn4uZmJj=QI4o3uBu) zCq#8;mfO3y64tk<(lb8G=aha<=}(U7=f%lvj|IM{*3811c|KaBY@Qm~#Pz2|MSWxP zGyJUjW&yrgJo3%CL*Jx+p68#a^^5$%3aMTE2eVV7Nq*`6y$8KPEF?@ycUM>M|MS&b`=6EGc{uJQ6Z7TR{l68+*CLa^0@i5w4pQT({HXgAn;5|M z!c3#{z>kwj*Y&#H9)t z^H$FvbVN6fnsoh$-=mxEx?1<$7Sxi=l=o_Hzj5yYAQHW zTJLYl2{2ZM5n(qKh(#@0q)Pg69Oq%_IF6j&ASs5N0(M$bx!;T82O{c2m>GhNgav%h zkT!fPr;t>T%pj>E8O5J+b0|9r1eK8vq(E${Bp-L|Ygl%?W5URsgaivS<8??jItqCNvuF4|pBTGmS8aavNIR6$wfsSV26#+W%5v3pZ@dRkceN2mxGX-4!j)>EWlL&G{ zE&x5&^eIrBhLJ%7IYUW;?N^o=@>q=`d_3I`x(|h{tIXGZRn|!^z~N9aU?o8eWQXjM%yWonh3wG=pq6_mV%H198cQu}fZ|b`pTQ1T zW0}n|)2T$+W8?B40e_<6^3mS#K|Hasl>Kjkc}5>kq$rNiV?UEE$i7Zu+Gd;cl;AJc zV!aJD`;tDVzmXbi0EiRm0qOdX)tc$#(c$$?H{gi|@FwNub?p5{FTULybh$*^62VC- zcm%swuUvH-%d76~<+aAGnwBO3L6)B*IjC=hH_>4C?|_N01K3l>Dpc3X`$ft=$U2CQ zRMkNBPf|+XSylO1Lc<43X#~Y}m;e;^7Q=$tqN9>xSz>em1*;p%y~ZI9}r}*!aU18YUnAZ>$iB7Za4AnQn0?H_E%ixshMN@@3o-@nuh| zrjj&jr;=RDgCgk%5{odYWV8Cg<*3kbto|nVx^du3+(BY$l01AUAp*G7uxK3HgZtMF zgZ27w&m4*u#;%*;)l!EZ4IcSIVt!>F3I21FDhGJMsJ>R`VtPuM>MoLr-iLcab*-u? z)l2G1NQ}N0`x`0+D!8%oRA8tYwXUVt?GM=UkprYi@C;6WRchyK)7EX%)OC$fM)iHu oEK>bF@E;f}?pygeY8i{vvP>Q1|D|%a&$Qxut7K{iYwi920n7?XR{#J2 literal 1330 zcmbtT&2G~`5Z<+UDT&_8}8gv5m$s9&H$g(6iPh!9nUVh@O`E8t5)v!zc;?&L*_m&~d9T+Iw0<6bioZ!AzO%8oJTkUv zmtRSM0GW!345ehD>4aKSqf;A!{R4L!0O&?LKQ?FQK`@X7YA-PH0}ge~3r z>_hvDgbpm7h+`SH3F%m*3q6Z0S!9`f`nr4IzyPkDc;wk3ya#jqI;>cHnehGsz6xs= z@5B0@7;gNa8pKdm?MKg_zR0IWBO8xZsh{Pb)0mGJ7ZGBMKkbsDMJPdp4oC{+RNWoa z%ILlLP*2oo=2m_bWon|MsOm(~B!_9rdN+#RPE}f;%mgyqR{VMy?3WtL0FKl+e}_r% zM3-Ze7lDeCEIG(=qRR&b=5KUtg3U!VdZpsA&LBXpp$t^2(bRIYIKl=!!TXeZ=L=6- zPL~vvfL<>u zl*z;aiK@;BD> B?q2`^ diff --git a/aciniformes_backend/routes/alert/__pycache__/reciever.cpython-310.pyc b/aciniformes_backend/routes/alert/__pycache__/reciever.cpython-310.pyc index a75858cc67eaa91be16a148c2c05e0df2e7e8e49..7288873877cd21d274851fc41695cb236f88ebb0 100644 GIT binary patch literal 2658 zcmZ`*UvCpf5Z~Q9pU=+OxrF>934cplxh*MGQ6DN=@vj9`)gY3H%CdC2+^)%)JKNnm z(L0zU=(1b2XU^!zmN6=?vTcem}$@QNioIUr%W*6>`-~*pPj*{D zH*Wet*a=%*8H?mB$l4-E$7SAzAKU}Hn-bwqntmi?+5`+k0T{0Iw&VnySAm{bK+43X z4%M>vBen>NVjF~_W~SjhJaauU``7s(Wo86-!Li~z_+ zb10h!G7UX3fs~0$wdDPVeFSQU8zu_5kp_emjf9Y{_xLEJb?Lxbi8#nAlEchFztOH7 zDTjhVD0{y_s6B2_mRp=@luFk`nkAH60KK|A3iQx}mtX>WjspY;IYppKsxpTevra_Y zbG@*07Xmx1l9fOwuBv2TFUd049?l0?2g8Oi7oh(MJvj&D5gpK7G9bHjkL=MKaCcrO z4~&#?7Ut4tl+%F`(r5H{9s9cWMSB5>VCAQqJPcADyqYPo;N%G)%IrguLj`be%xxX{ zlQ4Z6x`vuPoUs-$tqWY?V+Kp1P5Dxtv}TX4N718y-@y}5y774!-mBkS^S-~iUcXv1vM3hSbJdFZOm|;VUMrd3F2GDQ^4AW;o+mI%lJ*9grkBxbU3`~s+ zYa3f9a$L-`(6+dd<6_p=AaoUQKk9Sg_2Du}<;Spdt*ETb5P1q%`3aIwk&IxG7|B{8 z!(j!dG#Hpuhan;{+rNQ6M5MS(O{03C5y8245rJwGsd%G)Fd?w(gupx=*3U2<@ZZEL zk!N5EJhQSb-iVeq@b%@Ri{T4U{{gx#e=G)xl6vi!DzP6wk%5cvju3s8^UxshV3RG=)KM)ebElWu7 z|5z3Vd`@(w>#H%ls4V^XNM-imUC?!`vrJc)egagX=cmD@j)Hb>EMYYmh=Z~(WH0Nt zcnKsw0da`~NsGT3>K5ICYq|x?bPUUIsQHpXIy2@R*d>dZuNZY+*$`IS1`TTdm(8*R NW;ieH%J>TR;C}!dM>GHc literal 1333 zcmbtT&2G~`5Z<+UFJ&_8}8gv5m$s9&H$g(6iPh!9mTzF3QAOHCZxj5n1E;?%wa zS2*S+xbhCZazY$?SvdKOu-$TIo#b@#x50B)Rmyn~HBtb+DNDAfD-5u1* z=)L4nkJWJM)_xr4YOLe9?!@u9fJw%BH;&&;R5m-A3S_pe_!WiwmBuQBV>K#{Fb$vS zYGjHsR7skr2L+CG^{B++txin1xroNERWi~!gvd2ip~^Ix5OtF3BaNHoF*fKG-e=rC zUwG0Y*Vc#Z#x?DiFs})lgw7M5X-katNVHjDUaWN`v`Z)3w5xRz=hW>B?P?F?7R?Tk z=+^CBZDtJCP71?816$;dJ_~-JcGKL{{Ww;6UKnN4BCq23uQ@G~JLgq#EgjL7$Ve@& zWX4V1p>4SVu_vZ-csZi&5|>&x6WiuhzTlAX99*N%wNY_}PLbWOkJ);oO{}u)HhVEY zxIyB5Bm9uUN9>cHaFXYX_4A}Jg`Z06zVMVy+NB5X(XCl*F{C)-U}DGO z!9~P7{~eM3Hf{97i->ng8xDfHb)YJv$`qOF$S;pU None: - username, password = data.username, data.password - try: - await auth.registrate_user(username, password) - except exc.AlreadyRegistered as e: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=repr(e)) - - -@auth_router.get( - "/whoami", - status_code=status.HTTP_200_OK, - response_model=User, - responses={status.HTTP_401_UNAUTHORIZED: {"detail": "Unauthorized"}}, -) -async def get_current_user_info(_: Request, current_user=Depends(get_current_user)): - return User(id=current_user.id, username=current_user.username) diff --git a/aciniformes_backend/routes/base.py b/aciniformes_backend/routes/base.py index b264a00..ce7609e 100644 --- a/aciniformes_backend/routes/base.py +++ b/aciniformes_backend/routes/base.py @@ -1,19 +1,18 @@ from fastapi import FastAPI +from fastapi_sqlalchemy import DBSessionMiddleware + +from aciniformes_backend.settings import get_settings + from .alert.alert import router as alert_router from .alert.reciever import router as receiver_router from .fetcher import router as fetcher_router from .mectric import router as metric_router -from .auth import auth_router -from fastapi_sqlalchemy import DBSessionMiddleware -from aciniformes_backend.settings import get_settings - app = FastAPI() -app.include_router(alert_router, prefix="/alert") -app.include_router(auth_router, prefix="/auth") -app.include_router(receiver_router, prefix="/receiver") -app.include_router(fetcher_router, prefix="/fetcher") -app.include_router(metric_router, prefix="/metric") +app.include_router(alert_router, prefix="/alert", tags=["Alert"]) +app.include_router(receiver_router, prefix="/receiver", tags=["Receiver"]) +app.include_router(fetcher_router, prefix="/fetcher", tags=["Fetcher"]) +app.include_router(metric_router, prefix="/metric", tags=["Metric"]) app.add_middleware( DBSessionMiddleware, diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index 540bd3e..3b51ca3 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -1,13 +1,17 @@ +import logging + +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException -from starlette import status from pydantic import BaseModel, HttpUrl -from .auth import get_current_user -from aciniformes_backend.serivce import ( - FetcherServiceInterface, - fetcher_service, - exceptions as exc, -) +from starlette import status + +from aciniformes_backend.serivce import FetcherServiceInterface +from aciniformes_backend.serivce import exceptions as exc +from aciniformes_backend.serivce import fetcher_service + +logger = logging.getLogger(__name__) +router = APIRouter() class CreateSchema(BaseModel): @@ -40,17 +44,12 @@ class GetSchema(BaseModel): id: int -router = APIRouter() - - @router.post("", response_model=ResponsePostSchema) async def create( create_schema: CreateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), - token=Depends(get_current_user), + user=Depends(UnionAuth(["pinger.fetcher.create"])), ): - if not token: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) id_ = await fetcher.create(create_schema.dict()) return ResponsePostSchema(**create_schema.dict(), id=id_) @@ -80,10 +79,8 @@ async def update( id: int, update_schema: UpdateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), - token=Depends(get_current_user), + user=Depends(UnionAuth(["pinger.fetcher.update"])), ): - if not token: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) try: res = await fetcher.update(id, update_schema.dict(exclude_unset=True)) except exc.ObjectNotFound: @@ -95,8 +92,6 @@ async def update( async def delete( id: int, fetcher: FetcherServiceInterface = Depends(fetcher_service), - token=Depends(get_current_user), + user=Depends(UnionAuth(["pinger.fetcher.delete"])), ): - if not token: - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) await fetcher.delete(id) diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index 82416b6..986a8ab 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -1,13 +1,12 @@ +from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException +from pydantic import BaseModel from starlette import status -from pydantic import BaseModel, Json -from typing import Any -from aciniformes_backend.serivce import ( - MetricServiceInterface, - metric_service, - exceptions as exc, -) + +from aciniformes_backend.serivce import MetricServiceInterface +from aciniformes_backend.serivce import exceptions as exc +from aciniformes_backend.serivce import metric_service class CreateSchema(BaseModel): @@ -29,6 +28,7 @@ class GetSchema(BaseModel): async def create( metric_schema: CreateSchema, metric: MetricServiceInterface = Depends(metric_service), + user=Depends(UnionAuth(["pinger.metric.create"])), ): id_ = await metric.create(metric_schema.dict()) return ResponsePostSchema(**metric_schema.dict(), id=id_) diff --git a/aciniformes_backend/serivce/__init__.py b/aciniformes_backend/serivce/__init__.py index 823260b..90cfd65 100644 --- a/aciniformes_backend/serivce/__init__.py +++ b/aciniformes_backend/serivce/__init__.py @@ -1,29 +1,13 @@ -from .base import ( - BaseService, - AlertServiceInterface, - FetcherServiceInterface, - MetricServiceInterface, - ReceiverServiceInterface, - AuthServiceInterface, -) - -from .bootstrap import ( - receiver_service, - alert_service, - metric_service, - fetcher_service, - auth_service, - Config, -) - +from .base import (AlertServiceInterface, BaseService, FetcherServiceInterface, + MetricServiceInterface, ReceiverServiceInterface) +from .bootstrap import (Config, alert_service, fetcher_service, metric_service, + receiver_service) __all__ = [ "AlertServiceInterface", "FetcherServiceInterface", "MetricServiceInterface", "ReceiverServiceInterface", - "AuthServiceInterface", - "auth_service", "metric_service", "receiver_service", "alert_service", diff --git a/aciniformes_backend/serivce/alert.py b/aciniformes_backend/serivce/alert.py index 255b736..3677254 100644 --- a/aciniformes_backend/serivce/alert.py +++ b/aciniformes_backend/serivce/alert.py @@ -1,8 +1,9 @@ import sqlalchemy as sa -from .base import AlertServiceInterface -import aciniformes_backend.serivce.exceptions as exc import aciniformes_backend.models as db_models +import aciniformes_backend.serivce.exceptions as exc + +from .base import AlertServiceInterface class PgAlertService(AlertServiceInterface): diff --git a/aciniformes_backend/serivce/auth.py b/aciniformes_backend/serivce/auth.py deleted file mode 100644 index 6f231ed..0000000 --- a/aciniformes_backend/serivce/auth.py +++ /dev/null @@ -1,43 +0,0 @@ -from .base import AuthServiceInterface -import sqlalchemy as sa -import aciniformes_backend.serivce.exceptions as exc -from aciniformes_backend.settings import get_settings -import aciniformes_backend.models as db_models - -settings = get_settings() - - -class PgAuthService(AuthServiceInterface): - async def registrate_user(self, username, password) -> db_models.Auth | None: - q = ( - sa.insert(db_models.Auth) - .values(username=username, password=password) - .returning(db_models.Auth) - ) - if await self.get_user(username): - raise exc.AlreadyRegistered(username) - else: - return self.session.scalar(q) - - async def authenticate_user(self, username, password) -> db_models.Auth | None: - # first, compare with dotenv creds - password_from_settings = settings.ADMIN_SECRET.get(username) - if password_from_settings and await self._validate_password( - settings.PWD_CONTEXT.hash(password_from_settings), password - ): - return db_models.Auth(username=username, password=password) - db_user = await self.get_user(username) - if not db_user: - raise exc.NotRegistered(username) - if not await self._validate_password(db_user.password, password): - raise exc.WrongPassword() - return db_user - - async def get_user(self, username) -> db_models.Auth | None: - return self.session.scalar( - sa.select(db_models.Auth).where(db_models.Auth.username == username) - ) - - @staticmethod - async def _validate_password(db_password, inp_password): - return settings.PWD_CONTEXT.verify(inp_password, db_password) diff --git a/aciniformes_backend/serivce/base.py b/aciniformes_backend/serivce/base.py index 5face23..2429d9c 100644 --- a/aciniformes_backend/serivce/base.py +++ b/aciniformes_backend/serivce/base.py @@ -1,9 +1,9 @@ -from abc import abstractmethod, ABC +from abc import ABC, abstractmethod import pydantic +import sqlalchemy.orm import aciniformes_backend.models as db_models -import sqlalchemy.orm class BaseService(ABC): @@ -67,22 +67,22 @@ async def get_by_id(self, id_: int) -> db_models.Metric: raise NotImplementedError -class AuthServiceInterface(ABC): - def __init__(self, session: sqlalchemy.orm.Session | None): - self.session = session - - @abstractmethod - async def registrate_user(self, username, password) -> db_models.Auth | None: - raise NotImplementedError - - @abstractmethod - async def authenticate_user(self, username, password) -> db_models.Auth | None: - raise NotImplementedError - - @abstractmethod - async def get_user(self, username) -> db_models.Auth | None: - raise NotImplementedError - - @staticmethod - async def _validate_password(db_password, inp_password): - raise NotImplementedError +# class AuthServiceInterface(ABC): +# def __init__(self, session: sqlalchemy.orm.Session | None): +# self.session = session +# +# @abstractmethod +# async def registrate_user(self, username, password) -> db_models.Auth | None: +# raise NotImplementedError +# +# @abstractmethod +# async def authenticate_user(self, username, password) -> db_models.Auth | None: +# raise NotImplementedError +# +# @abstractmethod +# async def get_user(self, username) -> db_models.Auth | None: +# raise NotImplementedError +# +# @staticmethod +# async def _validate_password(db_password, inp_password): +# raise NotImplementedError diff --git a/aciniformes_backend/serivce/bootstrap.py b/aciniformes_backend/serivce/bootstrap.py index 77a20cf..cf9f70c 100644 --- a/aciniformes_backend/serivce/bootstrap.py +++ b/aciniformes_backend/serivce/bootstrap.py @@ -1,16 +1,11 @@ from fastapi_sqlalchemy import db -from .metric import PgMetricService + from .alert import PgAlertService -from .receiver import PgReceiverService +from .fake import (FakeAlertService, FakeFetcherService, FakeMetricService, + FakeReceiverService) from .fetcher import PgFetcherService -from .auth import PgAuthService -from .fake import ( - FakeAlertService, - FakeMetricService, - FakeReceiverService, - FakeFetcherService, - FakeAuthService, -) +from .metric import PgMetricService +from .receiver import PgReceiverService class Config: @@ -43,10 +38,3 @@ def fetcher_service(): return FakeFetcherService(None) with db(): return PgFetcherService(db.session) - - -def auth_service(): - if Config.fake: - return FakeAuthService(None) - with db(): - return PgAuthService(db.session) diff --git a/aciniformes_backend/serivce/fake.py b/aciniformes_backend/serivce/fake.py index 602322b..43fcfad 100644 --- a/aciniformes_backend/serivce/fake.py +++ b/aciniformes_backend/serivce/fake.py @@ -1,16 +1,12 @@ import pydantic -from .base import ( - AlertServiceInterface, - ReceiverServiceInterface, - FetcherServiceInterface, - MetricServiceInterface, - AuthServiceInterface, -) -import aciniformes_backend.serivce.exceptions as exc import aciniformes_backend.models as db_models +import aciniformes_backend.serivce.exceptions as exc from aciniformes_backend.settings import get_settings +from .base import (AlertServiceInterface, FetcherServiceInterface, + MetricServiceInterface, ReceiverServiceInterface) + class FakeAlertService(AlertServiceInterface): id_incr = 0 @@ -121,31 +117,3 @@ async def get_by_id(self, id_: int) -> db_models.Metric: async def get_all(self) -> list[db_models.BaseModel]: return list(self.repository.values()) - - -class FakeAuthService(AuthServiceInterface): - repository = [] - - async def registrate_user(self, username, password) -> db_models.Auth | None: - db_user = db_models.Auth(username=username, password=password) - self.repository.append(db_user) - return db_user - - async def authenticate_user(self, username, password) -> db_models.Auth | None: - for auth in self.repository: - if ( - self._validate_password(auth.password, password) - and auth.username == username - ): - return auth - raise exc.NotRegistered(username) - - async def get_user(self, username) -> db_models.Auth | None: - for auth in self.repository: - if auth.username == username: - return auth - raise exc.NotRegistered(username) - - @staticmethod - async def _validate_password(db_password, inp_password): - return get_settings().PWD_CONTEXT.verify(inp_password, db_password) diff --git a/aciniformes_backend/serivce/fetcher.py b/aciniformes_backend/serivce/fetcher.py index 6487008..e1be5f3 100644 --- a/aciniformes_backend/serivce/fetcher.py +++ b/aciniformes_backend/serivce/fetcher.py @@ -1,8 +1,9 @@ import sqlalchemy as sa -from .base import FetcherServiceInterface -import aciniformes_backend.serivce.exceptions as exc import aciniformes_backend.models as db_models +import aciniformes_backend.serivce.exceptions as exc + +from .base import FetcherServiceInterface class PgFetcherService(FetcherServiceInterface): diff --git a/aciniformes_backend/serivce/metric.py b/aciniformes_backend/serivce/metric.py index c5d5c84..95527b8 100644 --- a/aciniformes_backend/serivce/metric.py +++ b/aciniformes_backend/serivce/metric.py @@ -1,9 +1,10 @@ import sqlalchemy as sa -from .base import MetricServiceInterface import aciniformes_backend.models as db_models import aciniformes_backend.serivce.exceptions as exc +from .base import MetricServiceInterface + class PgMetricService(MetricServiceInterface): async def create(self, item: dict) -> int: diff --git a/aciniformes_backend/serivce/receiver.py b/aciniformes_backend/serivce/receiver.py index aea6005..494e9ab 100644 --- a/aciniformes_backend/serivce/receiver.py +++ b/aciniformes_backend/serivce/receiver.py @@ -2,9 +2,10 @@ import sqlalchemy as sa -from .base import ReceiverServiceInterface -import aciniformes_backend.serivce.exceptions as exc import aciniformes_backend.models as db_models +import aciniformes_backend.serivce.exceptions as exc + +from .base import ReceiverServiceInterface class PgReceiverService(ReceiverServiceInterface): diff --git a/aciniformes_backend/settings.py b/aciniformes_backend/settings.py index 73b05d6..c167344 100644 --- a/aciniformes_backend/settings.py +++ b/aciniformes_backend/settings.py @@ -1,7 +1,8 @@ -from pydantic import BaseSettings, PostgresDsn +import datetime from functools import lru_cache + from passlib.context import CryptContext -import datetime +from pydantic import BaseSettings, PostgresDsn class Settings(BaseSettings): diff --git a/alert_bot/__main__.py b/alert_bot/__main__.py index ebaf151..a88fa3c 100644 --- a/alert_bot/__main__.py +++ b/alert_bot/__main__.py @@ -1,6 +1,6 @@ -from .asgi import app import uvicorn +from .asgi import app if __name__ == "__main__": uvicorn.run(app, port=8002) diff --git a/alert_bot/asgi.py b/alert_bot/asgi.py index 916fad7..5a48554 100644 --- a/alert_bot/asgi.py +++ b/alert_bot/asgi.py @@ -1,9 +1,11 @@ -from fastapi import FastAPI, Depends -from aiogram.bot.bot import Bot +from datetime import datetime from functools import lru_cache -from alert_bot.settings import get_settings + +from aiogram.bot.bot import Bot +from fastapi import Depends, FastAPI from pydantic import BaseModel -from datetime import datetime + +from alert_bot.settings import get_settings app = FastAPI() diff --git a/alert_bot/settings.py b/alert_bot/settings.py index 6c58e74..6141988 100644 --- a/alert_bot/settings.py +++ b/alert_bot/settings.py @@ -1,6 +1,7 @@ -from pydantic import BaseSettings, HttpUrl from functools import lru_cache +from pydantic import BaseSettings, HttpUrl + class Settings(BaseSettings): BOT_TOKEN: str diff --git a/migrator/env.py b/migrator/env.py index 9d1ba3e..841a538 100644 --- a/migrator/env.py +++ b/migrator/env.py @@ -1,10 +1,10 @@ from logging.config import fileConfig -from aciniformes_backend.settings import get_settings -from aciniformes_backend.models import BaseModel -from sqlalchemy import engine_from_config -from sqlalchemy import pool + from alembic import context +from sqlalchemy import engine_from_config, pool +from aciniformes_backend.models import BaseModel +from aciniformes_backend.settings import get_settings config = context.config settings = get_settings() diff --git a/migrator/versions/85489ec3d0d0_auth.py b/migrator/versions/85489ec3d0d0_auth.py index 659ce25..924454a 100644 --- a/migrator/versions/85489ec3d0d0_auth.py +++ b/migrator/versions/85489ec3d0d0_auth.py @@ -5,9 +5,8 @@ Create Date: 2023-02-01 14:35:19.439045 """ -from alembic import op import sqlalchemy as sa - +from alembic import op # revision identifiers, used by Alembic. revision = "85489ec3d0d0" diff --git a/ping/__main__.py b/ping/__main__.py index 11e84df..6965e20 100644 --- a/ping/__main__.py +++ b/ping/__main__.py @@ -1,6 +1,6 @@ -from .api.asgi import ping_app import uvicorn +from .api.asgi import ping_app if __name__ == "__main__": uvicorn.run(ping_app, port=8001) diff --git a/ping/api/asgi.py b/ping/api/asgi.py index 04115a4..74a1687 100644 --- a/ping/api/asgi.py +++ b/ping/api/asgi.py @@ -1,10 +1,8 @@ -from ping.service import ( - SchedulerServiceInterface, - scheduler_service, -) -from fastapi import FastAPI, Depends, HTTPException +from fastapi import Depends, FastAPI, HTTPException from starlette import status + import ping.service.exceptions as exc +from ping.service import SchedulerServiceInterface, scheduler_service ping_app = FastAPI() diff --git a/ping/service/__init__.py b/ping/service/__init__.py index e4ffa12..95c2f69 100644 --- a/ping/service/__init__.py +++ b/ping/service/__init__.py @@ -1,7 +1,6 @@ +from .bootstrap import Config, crud_service, scheduler_service from .crud import CrudServiceInterface from .scheduler import SchedulerServiceInterface -from .bootstrap import crud_service, scheduler_service, Config - __all__ = [ "CrudServiceInterface", diff --git a/ping/service/crud.py b/ping/service/crud.py index befdd2c..edc0bda 100644 --- a/ping/service/crud.py +++ b/ping/service/crud.py @@ -2,8 +2,9 @@ import httpx -from aciniformes_backend.models import Fetcher, Alert -from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema +from aciniformes_backend.models import Alert, Fetcher +from aciniformes_backend.routes.mectric import \ + CreateSchema as MetricCreateSchema from ping.settings import get_settings @@ -38,16 +39,18 @@ async def get_alerts(self) -> list[Alert]: class FakeCrudService(CrudServiceInterface): id_incr = 0 fetcher_repo: dict[int, Fetcher] = { - 0: Fetcher(**{ - "name": "https://www.python.org", - "type_": "get_ok", - "address": "https://www.python.org", - "fetch_data": "string", - "metrics": {}, - "metric_name": "string", - "delay_ok": 30, - "delay_fail": 40, - }) + 0: Fetcher( + **{ + "name": "https://www.python.org", + "type_": "get_ok", + "address": "https://www.python.org", + "fetch_data": "string", + "metrics": {}, + "metric_name": "string", + "delay_ok": 30, + "delay_fail": 40, + } + ) } alert_repo: dict[int, Alert] = dict() metric_repo: dict[int, MetricCreateSchema] = dict() diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 5824d78..85ce2a3 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -1,13 +1,17 @@ +import time from abc import ABC, abstractmethod -from aciniformes_backend.models import Fetcher, FetcherType, Alert -from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from .crud import CrudServiceInterface -from .exceptions import AlreadyRunning -from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler -import httpx from datetime import datetime, timedelta + +import httpx +from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler + +from aciniformes_backend.models import Alert, Fetcher, FetcherType +from aciniformes_backend.routes.mectric import \ + CreateSchema as MetricCreateSchema from ping.settings import get_settings -import time + +from .crud import CrudServiceInterface +from .exceptions import AlreadyRunning settings = get_settings() diff --git a/ping/settings.py b/ping/settings.py index 2990724..94eec6a 100644 --- a/ping/settings.py +++ b/ping/settings.py @@ -1,6 +1,7 @@ -from pydantic import BaseSettings, HttpUrl from functools import lru_cache +from pydantic import BaseSettings, HttpUrl + class Settings(BaseSettings): BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" diff --git a/tests/backend/api/conftest.py b/tests/backend/api/conftest.py index 36842e0..edf2817 100644 --- a/tests/backend/api/conftest.py +++ b/tests/backend/api/conftest.py @@ -1,6 +1,7 @@ -import pytest import json +import pytest + @pytest.fixture def auth_user(client): diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index ebb5c18..66ff8c9 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -1,8 +1,10 @@ import json + import pytest from starlette import status + +from aciniformes_backend.serivce import Config, alert_service, receiver_service from aciniformes_backend.settings import get_settings -from aciniformes_backend.serivce import alert_service, receiver_service, Config def test_fake_service(fake_config): diff --git a/tests/backend/api/test_auth.py b/tests/backend/api/test_auth.py index 606c4a6..96b39b9 100644 --- a/tests/backend/api/test_auth.py +++ b/tests/backend/api/test_auth.py @@ -1,12 +1,11 @@ import json import pytest -import json - import sqlalchemy +from starlette import status + from aciniformes_backend.models import Auth from aciniformes_backend.serivce import auth_service -from starlette import status def test_auth_service(fake_config): diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 24cad30..3ff1852 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -1,7 +1,9 @@ import json + import pytest from starlette import status -from aciniformes_backend.serivce import fetcher_service, Config + +from aciniformes_backend.serivce import Config, fetcher_service def test_fake_service(fake_config): diff --git a/tests/backend/api/test_metric.py b/tests/backend/api/test_metric.py index b74e0eb..30aa6d1 100644 --- a/tests/backend/api/test_metric.py +++ b/tests/backend/api/test_metric.py @@ -1,7 +1,9 @@ import json + import pytest from starlette import status -from aciniformes_backend.serivce import metric_service, Config + +from aciniformes_backend.serivce import Config, metric_service @pytest.fixture diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index 9ae79a1..aa6be8c 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -1,11 +1,12 @@ import pytest -from sqlalchemy.orm import Session +from fastapi.testclient import TestClient from sqlalchemy import create_engine -from aciniformes_backend.settings import get_settings +from sqlalchemy.orm import Session + from aciniformes_backend.models.base import BaseModel from aciniformes_backend.routes.base import app from aciniformes_backend.serivce import Config -from fastapi.testclient import TestClient +from aciniformes_backend.settings import get_settings @pytest.fixture(scope="session") diff --git a/tests/backend/service/conftest.py b/tests/backend/service/conftest.py index 2f0af92..778ddb9 100644 --- a/tests/backend/service/conftest.py +++ b/tests/backend/service/conftest.py @@ -1,11 +1,8 @@ import pytest -from aciniformes_backend.serivce import ( - alert_service, - fetcher_service, - receiver_service, - metric_service, - Config, -) + +from aciniformes_backend.serivce import (Config, alert_service, + fetcher_service, metric_service, + receiver_service) @pytest.fixture diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index 63bddd6..b5481fa 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -1,13 +1,14 @@ import json + import pytest import sqlalchemy -from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema -from aciniformes_backend.routes.alert.reciever import ( - CreateSchema as ReceiverCreateSchema, -) -from aciniformes_backend.models import Alert, Receiver import aciniformes_backend.serivce.exceptions as exc +from aciniformes_backend.models import Alert, Receiver +from aciniformes_backend.routes.alert.alert import \ + CreateSchema as AlertCreateSchema +from aciniformes_backend.routes.alert.reciever import \ + CreateSchema as ReceiverCreateSchema @pytest.fixture diff --git a/tests/backend/service/test_fetcher_service.py b/tests/backend/service/test_fetcher_service.py index c7be91b..7867ab0 100644 --- a/tests/backend/service/test_fetcher_service.py +++ b/tests/backend/service/test_fetcher_service.py @@ -1,8 +1,9 @@ import pytest import sqlalchemy -from aciniformes_backend.routes.fetcher import CreateSchema as FetcherCreateSchema from aciniformes_backend.models import Fetcher +from aciniformes_backend.routes.fetcher import \ + CreateSchema as FetcherCreateSchema @pytest.fixture diff --git a/tests/backend/service/test_metric_service.py b/tests/backend/service/test_metric_service.py index cea243d..f6a9e63 100644 --- a/tests/backend/service/test_metric_service.py +++ b/tests/backend/service/test_metric_service.py @@ -1,9 +1,10 @@ import pytest import sqlalchemy -from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from aciniformes_backend.models import Metric import aciniformes_backend.serivce.exceptions as exc +from aciniformes_backend.models import Metric +from aciniformes_backend.routes.mectric import \ + CreateSchema as MetricCreateSchema @pytest.fixture diff --git a/tests/ping_service/api/test_asgi.py b/tests/ping_service/api/test_asgi.py index 4d3cf66..cc1d17f 100644 --- a/tests/ping_service/api/test_asgi.py +++ b/tests/ping_service/api/test_asgi.py @@ -22,10 +22,10 @@ def test_stop(self, ping_client): assert res.status_code == status.HTTP_200_OK def test_get_active_jobs(self, ping_client): - res = ping_client.get('/fetchers_active') + res = ping_client.get("/fetchers_active") assert res.status_code == status.HTTP_200_OK assert len(res.json()) > 0 def test_delete_schedulers(self, ping_client): - res = ping_client.delete('/schedule') + res = ping_client.delete("/schedule") assert res.status_code == status.HTTP_200_OK diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index c9f3a23..38b38f3 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -1,8 +1,9 @@ import pytest -from ping.api.asgi import ping_app from fastapi.testclient import TestClient -from ping.service import Config + from aciniformes_backend.routes import app +from ping.api.asgi import ping_app +from ping.service import Config @pytest.fixture(scope="session") diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index 1cb876a..940affa 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -1,6 +1,7 @@ -from ping.service import Config, scheduler_service, crud_service import pytest +from ping.service import Config, crud_service, scheduler_service + @pytest.fixture def pg_config(): diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index fc99035..ac6c51d 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,29 +1,39 @@ import pytest -from aciniformes_backend.models import Fetcher + import ping.service.exceptions as exc +from aciniformes_backend.models import Fetcher @pytest.fixture() def fetcher_obj(): - yield Fetcher(**{ - "name": "https://www.python.org", - "type_": "get_ok", - "address": "https://www.python.org", - "fetch_data": "string", - "metrics": {}, - "metric_name": "string", - "delay_ok": 30, - "delay_fail": 40, - }) + yield Fetcher( + **{ + "name": "https://www.python.org", + "type_": "get_ok", + "address": "https://www.python.org", + "fetch_data": "string", + "metrics": {}, + "metric_name": "string", + "delay_ok": 30, + "delay_fail": 40, + } + ) + class TestSchedulerService: @pytest.mark.asyncio - async def test_add_fetcher_success(self, pg_scheduler_service, fake_crud_service, fetcher_obj): + async def test_add_fetcher_success( + self, pg_scheduler_service, fake_crud_service, fetcher_obj + ): pg_scheduler_service.add_fetcher(fetcher_obj) @pytest.mark.asyncio - async def test_delete_fetcher(self, pg_scheduler_service, fake_crud_service, fetcher_obj): - pg_scheduler_service.delete_fetcher(f"{fetcher_obj.name} {fetcher_obj.create_ts}") + async def test_delete_fetcher( + self, pg_scheduler_service, fake_crud_service, fetcher_obj + ): + pg_scheduler_service.delete_fetcher( + f"{fetcher_obj.name} {fetcher_obj.create_ts}" + ) @pytest.mark.asyncio async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): From 80dab9a6885d4dfc3972f744513eb46689fc4d0a Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Mon, 10 Apr 2023 14:04:59 +0300 Subject: [PATCH 12/53] Deleting __pycache__ --- .../__pycache__/__init__.cpython-310.pyc | Bin 190 -> 0 bytes .../routes/__pycache__/__init__.cpython-310.pyc | Bin 229 -> 0 bytes .../routes/__pycache__/base.cpython-310.pyc | Bin 871 -> 0 bytes .../routes/__pycache__/fetcher.cpython-310.pyc | Bin 3007 -> 0 bytes .../routes/__pycache__/mectric.cpython-310.pyc | Bin 1849 -> 0 bytes .../alert/__pycache__/__init__.cpython-310.pyc | Bin 203 -> 0 bytes .../alert/__pycache__/alert.cpython-310.pyc | Bin 2702 -> 0 bytes .../alert/__pycache__/reciever.cpython-310.pyc | Bin 2658 -> 0 bytes 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 aciniformes_backend/__pycache__/__init__.cpython-310.pyc delete mode 100644 aciniformes_backend/routes/__pycache__/__init__.cpython-310.pyc delete mode 100644 aciniformes_backend/routes/__pycache__/base.cpython-310.pyc delete mode 100644 aciniformes_backend/routes/__pycache__/fetcher.cpython-310.pyc delete mode 100644 aciniformes_backend/routes/__pycache__/mectric.cpython-310.pyc delete mode 100644 aciniformes_backend/routes/alert/__pycache__/__init__.cpython-310.pyc delete mode 100644 aciniformes_backend/routes/alert/__pycache__/alert.cpython-310.pyc delete mode 100644 aciniformes_backend/routes/alert/__pycache__/reciever.cpython-310.pyc diff --git a/aciniformes_backend/__pycache__/__init__.cpython-310.pyc b/aciniformes_backend/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 42cbe5111d8b5cb905e83b5617a4bd5057b02c87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190 zcmd1j<>g`kg5)K}DIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o1Tv$aS`g2`x@7 zDvr6Z@M6=24Hp|@T=J7kb5rw5ieua}OFT-GVhW1#(~|RZV-k}y^D@)&i*i$ob%A18 usmUeCV)03d$=RuSDKYW!Ky@YY@p=W7w>WHa^HWN5Qtd!i7c&6~76t$d@-$BX diff --git a/aciniformes_backend/routes/__pycache__/__init__.cpython-310.pyc b/aciniformes_backend/routes/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 93187d2920e42e5d4cfb2804ada0c90ab82f68f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 229 zcmd1j<>g`kg5)K}DdIr-F^Gc(44TX@8G*u@ zjJKE*3kv)+nQpNpB^IX^fi$gTC}IIpVB%{kkmYO@6AF|oj=8Y#V$+2U7aL<-@{>z* zQ}arSW85=KJW7*d3X1a6lJj$85|cCYGSl*la#M?Sfnr&y$tB2Q@kxov*{OLcF-7^M kC8@pF diff --git a/aciniformes_backend/routes/__pycache__/base.cpython-310.pyc b/aciniformes_backend/routes/__pycache__/base.cpython-310.pyc deleted file mode 100644 index af2d6bae427a817242f30f2088a492d4adeb6854..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 871 zcmY*XzjM z!pO#-z@N~rSz+lPz{nlXPEvw&e){x1`RV&4Wt~pTqBi;U7`AJc^|PF;H-{$A=`%kl zfCWrh3FC}eU_(vWNsZT+xIx%iVwbSD#7)92!WPt3JMp<+;fCrY0S}mk?Aqd8?4EC6 z9UO3{47|@Z;!DPXCba%zJZOXeAJc*k1Z&JD`WReW=RN4c25f$@p;y*}trM&N;Er5a zeKvOwgei7k9p16U+|k*rKuwvg;ob=vBeQfQ0TjL$8j1F&SVRVkLZ(x5IoN;d}@W0V)Dx%YaBuDrnni}($3?!f|Fxq}1?Eo1JCs_~S!cF#_a z_m4-T!_&UaJ9(C=h)PE}ZOgmTWJ(kyEmC-aidHxRfgC+U7Np=RA;UtY(0#f`I^tlKUIs&Tuap zo*aF%bd&z6!f!!n|4DdeP@C}P>+PqT@3$YrVHVF4ObZjfkj2Z{ILvi6iL)eJQ}T$o zb8@oIt<;6pA(?P24DRHYbMI-A!A#+^^68ju3clsDf8D9|*rL~Mn>oy5tv`POrn~8Tp)UZpbKBnZ0eV+L&l!7{4Fp?rX6Bq3&zbLhXU3gMrAXju{e6c2 zSRmvt>9!S?ql#0Bs!lZ;acWv;hojD@7~LHcNA{>QF6a(%Caw^k=Y>av7es!?a3-N& z>IOuPLG*`G4p&d zMz48nuqKW_E~fPyi_h}~J!b*tEWS2p@xYwa&lCI|J${kfE2RGJKNzbzQ-upRuHNkQ z5+Rj+NpwY<$91YI%d4w5zWv1)-6ZI=L1D#-m-J$saM6p!wGJ1dvX_&jyCy@h7uMP^ z@Io)SRX5bsWs&%|gj^BwPT-5HZTNc2^Mx8|r3N=nEvhK`Ys3v7JsN%t=$wZVH$VvD zP);1SMuZ_uPS2$^%W5O5O{jBV&t-iJY76>yR_D(FCt&|YRsKc_FA*!?Xyhrg?L|W6 zlC7?AmF;mZMI5W5_SfZJ;wd{4i46Q$mC}Z*XXF6~Z_Dl6R>ic{@`A8Yw^iQN16)@X zT{r6RUMQelcHN(PUWiknXT*tABd+VU+Z})x0E|Iv^1x4&5ww%1M9#t1wfL7q>zl8d zYXDu`e75)e;j=%UKWJX+_`OK9lel>$NS1r+&93aU{7%&L{Gc7QIx-URNziQyKN;4! z>z;obg58uD`?#3}W~sX+%dpTWlz0q8k>u&{Tc+Cn_E^opq~Ta`dXWJB5GPIncSDWc z6mb`JDsFV*Bt^fWSiqm=lPIQ8%%V7oVt_*0I)=LAAW$EhAc~|$wdU=PwPD!d zjtL`k5=oTH+DN3n9?4{7dL&d|kEF6QJrHZqE@YbplC5=tJQ>LvvI0KHDvA*lH53T7 zM5yHv6yqT3`PZSCMw5oZ#ukE{!gClhi(p-Vk_E6#hhM-6?Qh2k@gK%1O*YPS@CsK% z7Be{y;~H`SBD_1%4cZ$*F7-L+>%>%*w07e( zL+#A$Fz>0lOwjtIgN$4iqnxO>W#7N9d$c)GIA^jO{ zjmA@|O6iT@`Vhmqr3&cq`j#7TWx%1Xs@K=?%xQFz%bi}EON^AnP^mGzTimm!&$^B4 ztM29NYmG~FBMkzAEI&c-2WU)G0Y*yj82%hIY4Y7I?zNM^mzZD@PeE1l z;CI6fgY~8UhcTXNcIfKsUvo>4x4|7>NcdAF5`SD}2?a(%*dMJV-4>z_Ck%QPq0YU&lb5{;sKGY?Z z)~*A@ajxkf=*0$d$*upPe;~Q;u|4%1^irT{XO@yJ6wL}dn8$Kw=9}43)@(Kite=1T zod0DK@;eIC%L3sB?A)S+aKdR!66(=}d5ogWVk0p_?19PKeEw-rKyNjS3-EbxjOK%5#^h=Ely> zy{~@=#juF76ckoo1jPtV^;e-}c|`!CO5J$d=dtCRj(7LF2;7I}X?DmF%Y{h`bTVV3lRFiN9ACKHi=3c3Rk z7SlR^F9^Q{Wcw0<&ie`20jTcqP}<(YRy|=$yednG%TFnA^;5oXsCv! zeP855NJ!kv@}dN&r&z>aRAdXjm9t13Aj*i+Lbg%7$h+h^s6+hcVCQ9E4%M2s1}10V zEL5hxF2-Kv5@>pI8R-8yz)TjtqMDBTybTh_Q6>cXq$q@~IZ zuu3t+?wKr%y8`=H*!fi;C-j(}kz;a3&&fI6gcrU+9vKDWEE;Q{Q%;YKh(4!3X)t>_ z2OT}6W|h7rqN2fYP5CbJ?*UQf2zmuD<^EK*8p-#;{0{6K=Ma0`u~w->T?o_YVlm)w z>uB*ah?bm@b25$-Vu$k59w1cR7lj|hvCB%&1|ardpC7^vAHyE|Y%!Bc)E!km`!V8= zbY3C)n=C}G23!080+KB3>kK*0!S z%3;OVQ@)*il)URK=6Mk$wADT$?0Z5%uPwX4_s-qwzPXKS~& z<{D)Y5M=oglFM#IoRk5#e*#R7?adxD)~2S>dSg@ek90xRb5~S#5I{)7NMgzob5-rJ zs(U$%bRen@CuJ-Om|i-j+VGGEX%U4I>!+LmhMgZrd)>*MRa9S@8Ud5%-i3~c9)?09 zu(FN>|EMGuMn7}iMN+rErALEQ<`AB(ZVw&2E}C4y4=&TzoX)*=y&Q30VJqc`f&(L@ g{B@{Pbxg;!Oxv&wo0@+!fP^voO|8C6Yn2b^zdc#0ApigX diff --git a/aciniformes_backend/routes/alert/__pycache__/__init__.cpython-310.pyc b/aciniformes_backend/routes/alert/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index d31a21fd71770d99acc8ca52190e40e21cd67622..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203 zcmd1j<>g`kg5)K}DIoeWh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o1T+$aS`g2`x@7 zDvr6Z@M6=24Hp|@T=J7kb5rw5ieua}OFT-GVhW1#(~|RZV-k}y^D@)&i*i$ob%A18 zsmUeCV)03d$=RuSDKSO)r6sAwF^M^;MI|xu@jy)_@$q^EmA5!-a`RJ4b5iXxegmA)XNIKM^6nPf1j^=2J*FwExIELbNSYtY-;*BuZu^dbB zW|;33oPy$W;Y6qC6gwrS)G0fYl-wcQ;`tYZ=Y_GYIX27-d;;bZ!iM=2%!|AP^ODk6 zU|!~vFrQTOX+gJ%Qx(<^Xn|KA(rx&)7@rn4uZmJj=QI4o3uBu) zCq#8;mfO3y64tk<(lb8G=aha<=}(U7=f%lvj|IM{*3811c|KaBY@Qm~#Pz2|MSWxP zGyJUjW&yrgJo3%CL*Jx+p68#a^^5$%3aMTE2eVV7Nq*`6y$8KPEF?@ycUM>M|MS&b`=6EGc{uJQ6Z7TR{l68+*CLa^0@i5w4pQT({HXgAn;5|M z!c3#{z>kwj*Y&#H9)t z^H$FvbVN6fnsoh$-=mxEx?1<$7Sxi=l=o_Hzj5yYAQHW zTJLYl2{2ZM5n(qKh(#@0q)Pg69Oq%_IF6j&ASs5N0(M$bx!;T82O{c2m>GhNgav%h zkT!fPr;t>T%pj>E8O5J+b0|9r1eK8vq(E${Bp-L|Ygl%?W5URsgaivS<8??jItqCNvuF4|pBTGmS8aavNIR6$wfsSV26#+W%5v3pZ@dRkceN2mxGX-4!j)>EWlL&G{ zE&x5&^eIrBhLJ%7IYUW;?N^o=@>q=`d_3I`x(|h{tIXGZRn|!^z~N9aU?o8eWQXjM%yWonh3wG=pq6_mV%H198cQu}fZ|b`pTQ1T zW0}n|)2T$+W8?B40e_<6^3mS#K|Hasl>Kjkc}5>kq$rNiV?UEE$i7Zu+Gd;cl;AJc zV!aJD`;tDVzmXbi0EiRm0qOdX)tc$#(c$$?H{gi|@FwNub?p5{FTULybh$*^62VC- zcm%swuUvH-%d76~<+aAGnwBO3L6)B*IjC=hH_>4C?|_N01K3l>Dpc3X`$ft=$U2CQ zRMkNBPf|+XSylO1Lc<43X#~Y}m;e;^7Q=$tqN9>xSz>em1*;p%y~ZI9}r}*!aU18YUnAZ>$iB7Za4AnQn0?H_E%ixshMN@@3o-@nuh| zrjj&jr;=RDgCgk%5{odYWV8Cg<*3kbto|nVx^du3+(BY$l01AUAp*G7uxK3HgZtMF zgZ27w&m4*u#;%*;)l!EZ4IcSIVt!>F3I21FDhGJMsJ>R`VtPuM>MoLr-iLcab*-u? z)l2G1NQ}N0`x`0+D!8%oRA8tYwXUVt?GM=UkprYi@C;6WRchyK)7EX%)OC$fM)iHu oEK>bF@E;f}?pygeY8i{vvP>Q1|D|%a&$Qxut7K{iYwi920n7?XR{#J2 diff --git a/aciniformes_backend/routes/alert/__pycache__/reciever.cpython-310.pyc b/aciniformes_backend/routes/alert/__pycache__/reciever.cpython-310.pyc deleted file mode 100644 index 7288873877cd21d274851fc41695cb236f88ebb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2658 zcmZ`*UvCpf5Z~Q9pU=+OxrF>934cplxh*MGQ6DN=@vj9`)gY3H%CdC2+^)%)JKNnm z(L0zU=(1b2XU^!zmN6=?vTcem}$@QNioIUr%W*6>`-~*pPj*{D zH*Wet*a=%*8H?mB$l4-E$7SAzAKU}Hn-bwqntmi?+5`+k0T{0Iw&VnySAm{bK+43X z4%M>vBen>NVjF~_W~SjhJaauU``7s(Wo86-!Li~z_+ zb10h!G7UX3fs~0$wdDPVeFSQU8zu_5kp_emjf9Y{_xLEJb?Lxbi8#nAlEchFztOH7 zDTjhVD0{y_s6B2_mRp=@luFk`nkAH60KK|A3iQx}mtX>WjspY;IYppKsxpTevra_Y zbG@*07Xmx1l9fOwuBv2TFUd049?l0?2g8Oi7oh(MJvj&D5gpK7G9bHjkL=MKaCcrO z4~&#?7Ut4tl+%F`(r5H{9s9cWMSB5>VCAQqJPcADyqYPo;N%G)%IrguLj`be%xxX{ zlQ4Z6x`vuPoUs-$tqWY?V+Kp1P5Dxtv}TX4N718y-@y}5y774!-mBkS^S-~iUcXv1vM3hSbJdFZOm|;VUMrd3F2GDQ^4AW;o+mI%lJ*9grkBxbU3`~s+ zYa3f9a$L-`(6+dd<6_p=AaoUQKk9Sg_2Du}<;Spdt*ETb5P1q%`3aIwk&IxG7|B{8 z!(j!dG#Hpuhan;{+rNQ6M5MS(O{03C5y8245rJwGsd%G)Fd?w(gupx=*3U2<@ZZEL zk!N5EJhQSb-iVeq@b%@Ri{T4U{{gx#e=G)xl6vi!DzP6wk%5cvju3s8^UxshV3RG=)KM)ebElWu7 z|5z3Vd`@(w>#H%ls4V^XNM-imUC?!`vrJc)egagX=cmD@j)Hb>EMYYmh=Z~(WH0Nt zcnKsw0da`~NsGT3>K5ICYq|x?bPUUIsQHpXIy2@R*d>dZuNZY+*$`IS1`TTdm(8*R NW;ieH%J>TR;C}!dM>GHc From caef12dd37c41109a08dede7f75539e4052cc667 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 10 Apr 2023 14:10:03 +0300 Subject: [PATCH 13/53] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4b8ff68..590cb52 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,5 @@ python-multipart passlib~=1.7.4 bcrypt APScheduler~=3.10.0 -jose~=1.0.0 \ No newline at end of file +jose~=1.0.0 +auth-lib-profcomff~=2023.3.16.2 \ No newline at end of file From 7142f361fa00a166a552dbdd2d7117819d557fe4 Mon Sep 17 00:00:00 2001 From: Wudext Date: Thu, 13 Apr 2023 13:17:53 +0300 Subject: [PATCH 14/53] Test fix --- aciniformes_backend/serivce/__init__.py | 18 ++++-- aciniformes_backend/serivce/bootstrap.py | 8 ++- aciniformes_backend/serivce/fake.py | 8 ++- ping/service/crud.py | 3 +- ping/service/scheduler.py | 3 +- requirements.txt | 3 +- tests/backend/api/conftest.py | 60 ++++++++++++------- tests/backend/api/test_alert.py | 17 +++--- tests/backend/api/test_auth.py | 30 ---------- tests/backend/api/test_fetcher.py | 15 +++-- tests/backend/service/conftest.py | 10 +++- tests/backend/service/test_alert_serivce.py | 8 +-- tests/backend/service/test_fetcher_service.py | 3 +- tests/backend/service/test_metric_service.py | 3 +- 14 files changed, 95 insertions(+), 94 deletions(-) delete mode 100644 tests/backend/api/test_auth.py diff --git a/aciniformes_backend/serivce/__init__.py b/aciniformes_backend/serivce/__init__.py index 90cfd65..1494168 100644 --- a/aciniformes_backend/serivce/__init__.py +++ b/aciniformes_backend/serivce/__init__.py @@ -1,7 +1,17 @@ -from .base import (AlertServiceInterface, BaseService, FetcherServiceInterface, - MetricServiceInterface, ReceiverServiceInterface) -from .bootstrap import (Config, alert_service, fetcher_service, metric_service, - receiver_service) +from .base import ( + AlertServiceInterface, + BaseService, + FetcherServiceInterface, + MetricServiceInterface, + ReceiverServiceInterface, +) +from .bootstrap import ( + Config, + alert_service, + fetcher_service, + metric_service, + receiver_service, +) __all__ = [ "AlertServiceInterface", diff --git a/aciniformes_backend/serivce/bootstrap.py b/aciniformes_backend/serivce/bootstrap.py index cf9f70c..a5de380 100644 --- a/aciniformes_backend/serivce/bootstrap.py +++ b/aciniformes_backend/serivce/bootstrap.py @@ -1,8 +1,12 @@ from fastapi_sqlalchemy import db from .alert import PgAlertService -from .fake import (FakeAlertService, FakeFetcherService, FakeMetricService, - FakeReceiverService) +from .fake import ( + FakeAlertService, + FakeFetcherService, + FakeMetricService, + FakeReceiverService, +) from .fetcher import PgFetcherService from .metric import PgMetricService from .receiver import PgReceiverService diff --git a/aciniformes_backend/serivce/fake.py b/aciniformes_backend/serivce/fake.py index 43fcfad..79b3cf3 100644 --- a/aciniformes_backend/serivce/fake.py +++ b/aciniformes_backend/serivce/fake.py @@ -4,8 +4,12 @@ import aciniformes_backend.serivce.exceptions as exc from aciniformes_backend.settings import get_settings -from .base import (AlertServiceInterface, FetcherServiceInterface, - MetricServiceInterface, ReceiverServiceInterface) +from .base import ( + AlertServiceInterface, + FetcherServiceInterface, + MetricServiceInterface, + ReceiverServiceInterface, +) class FakeAlertService(AlertServiceInterface): diff --git a/ping/service/crud.py b/ping/service/crud.py index edc0bda..bca921e 100644 --- a/ping/service/crud.py +++ b/ping/service/crud.py @@ -3,8 +3,7 @@ import httpx from aciniformes_backend.models import Alert, Fetcher -from aciniformes_backend.routes.mectric import \ - CreateSchema as MetricCreateSchema +from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from ping.settings import get_settings diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 85ce2a3..17cd1ed 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -6,8 +6,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler from aciniformes_backend.models import Alert, Fetcher, FetcherType -from aciniformes_backend.routes.mectric import \ - CreateSchema as MetricCreateSchema +from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from ping.settings import get_settings from .crud import CrudServiceInterface diff --git a/requirements.txt b/requirements.txt index 590cb52..6ebd592 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,5 @@ passlib~=1.7.4 bcrypt APScheduler~=3.10.0 jose~=1.0.0 -auth-lib-profcomff~=2023.3.16.2 \ No newline at end of file +auth-lib-profcomff~=2023.3.16.2 +asyncio~=3.4.3 diff --git a/tests/backend/api/conftest.py b/tests/backend/api/conftest.py index edf2817..335481b 100644 --- a/tests/backend/api/conftest.py +++ b/tests/backend/api/conftest.py @@ -1,28 +1,42 @@ -import json - import pytest +from fastapi.testclient import TestClient +from pytest_mock import MockerFixture - -@pytest.fixture -def auth_user(client): - body = {"username": "test", "password": "test"} - res = client.post("/auth/register", data=json.dumps(body)) - assert res.status_code == 201 - return body +from aciniformes_backend.routes.base import app @pytest.fixture -def auth_header(client, auth_user): - beaver = client.post( - f"/auth/token", - data={ - "username": auth_user["username"], - "password": auth_user["password"], - "grant_type": "password", - }, - headers={"content-type": "application/x-www-form-urlencoded"}, - ) - assert beaver.status_code == 200 - auth_data = json.loads(beaver.content) - auth_headers = {"Authorization": f"Bearer {auth_data.get('access_token')}"} - return auth_headers +def client(mocker: MockerFixture): + user_mock = mocker.patch("auth_lib.fastapi.UnionAuth.__call__") + user_mock.return_value = { + "session_scopes": [ + {"id": 53, "name": "pinger.alert.create"}, + {"id": 56, "name": "pinger.receiver.create"}, + {"id": 61, "name": "pinger.fetcher.update"}, + {"id": 62, "name": "pinger.metric.create"}, + {"id": 60, "name": "pinger.fetcher.delete"}, + {"id": 57, "name": "pinger.receiver.delete"}, + {"id": 54, "name": "pinger.alert.delete"}, + {"id": 58, "name": "pinger.receiver.update"}, + {"id": 59, "name": "pinger.fetcher.create"}, + {"id": 55, "name": "pinger.alert.update"}, + ], + "user_scopes": [ + {"id": 53, "name": "pinger.alert.create"}, + {"id": 56, "name": "pinger.receiver.create"}, + {"id": 61, "name": "pinger.fetcher.update"}, + {"id": 62, "name": "pinger.metric.create"}, + {"id": 60, "name": "pinger.fetcher.delete"}, + {"id": 57, "name": "pinger.receiver.delete"}, + {"id": 54, "name": "pinger.alert.delete"}, + {"id": 58, "name": "pinger.receiver.update"}, + {"id": 59, "name": "pinger.fetcher.create"}, + {"id": 55, "name": "pinger.alert.update"}, + ], + "indirect_groups": [{"id": 0, "name": "string", "parent_id": 0}], + "groups": [{"id": 0, "name": "string", "parent_id": 0}], + "id": 0, + "email": "string", + } + client = TestClient(app) + return client diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 66ff8c9..5c8e9af 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -102,9 +102,9 @@ class TestReceiver: Config.fake = True s = receiver_service() - def test_post_success(self, client, auth_header): - body = {"name": "string", "chat_id": 0} - res = client.post(self._url, data=json.dumps(body), headers=auth_header) + def test_post_success(self, client): + body = {"name": "test", "chat_id": 0} + res = client.post(self._url, data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["name"] == body["name"] @@ -118,8 +118,8 @@ def test_get_by_id_success(self, client, this_receiver): assert res_body["name"] == this_receiver["name"] assert res_body["chat_id"] == this_receiver["chat_id"] - def test_delete_by_id_success(self, client, this_receiver, auth_header): - res = client.delete(f"{self._url}/{this_receiver['id']}", headers=auth_header) + def test_delete_by_id_success(self, client, this_receiver): + res = client.delete(f"{self._url}/{this_receiver['id']}") assert res.status_code == status.HTTP_200_OK assert self.s.repository[this_receiver["id"]] is None @@ -128,12 +128,11 @@ def test_get_success(self, client, this_receiver): assert res.status_code == status.HTTP_200_OK assert len(res.json()) - def test_patch_by_id_success(self, client, this_receiver, auth_header): + def test_patch_by_id_success(self, client, this_receiver): body = {"name": "s", "chat_id": 11} res = client.patch( f"{self._url}/{this_receiver['id']}", data=json.dumps(body), - headers=auth_header, ) assert res.status_code == status.HTTP_200_OK res_body = res.json() @@ -144,9 +143,9 @@ def test_get_by_id_not_found(self, client): res = client.get(f"{self._url}/{888}") assert res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_not_found(self, client, auth_header): + def test_patch_by_id_not_found(self, client): body = {"name": "st", "chat_id": 0} res = client.patch( - f"{self._url}/{888}", data=json.dumps(body), headers=auth_header + f"{self._url}/{888}", data=json.dumps(body) ) assert res.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/backend/api/test_auth.py b/tests/backend/api/test_auth.py deleted file mode 100644 index 96b39b9..0000000 --- a/tests/backend/api/test_auth.py +++ /dev/null @@ -1,30 +0,0 @@ -import json - -import pytest -import sqlalchemy -from starlette import status - -from aciniformes_backend.models import Auth -from aciniformes_backend.serivce import auth_service - - -def test_auth_service(fake_config): - s = auth_service() - assert s.session is None - assert type(s.repository) is list - - -@pytest.fixture -def registered_user(client): - body = {"username": "test", "password": "test"} - res = client.post("/auth/register", data=json.dumps(body)) - assert res.status_code == status.HTTP_201_CREATED - - -class TestAuth: - _url = "/auth" - - def test_create_user(self, dbsession, client): - body = {"username": "test", "password": "test"} - res = client.post("/auth/register", data=json.dumps(body)) - assert res.status_code == status.HTTP_201_CREATED diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 3ff1852..d43d89f 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -34,7 +34,7 @@ class TestFetcher: Config.fake = True s = fetcher_service() - def test_post_success(self, client, auth_header): + def test_post_success(self, client): body = { "name": "string", "type_": "get_ok", @@ -45,7 +45,7 @@ def test_post_success(self, client, auth_header): "delay_ok": 0, "delay_fail": 0, } - res = client.post(self._url, data=json.dumps(body), headers=auth_header) + res = client.post(self._url, data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["id"] is not None @@ -55,8 +55,8 @@ def test_get_by_id_success(self, client, this_fetcher): assert res.status_code == status.HTTP_200_OK assert res.json()["address"] == this_fetcher["address"] - def test_delete_by_id_success(self, client, this_fetcher, auth_header): - res = client.delete(f"{self._url}/{this_fetcher['id']}", headers=auth_header) + def test_delete_by_id_success(self, client, this_fetcher): + res = client.delete(f"{self._url}/{this_fetcher['id']}") assert res.status_code == status.HTTP_200_OK assert self.s.repository[this_fetcher["id"]] is None @@ -65,12 +65,11 @@ def test_get_success(self, client, this_fetcher): assert res.status_code == status.HTTP_200_OK assert len(res.json()) - def test_patch_by_id_success(self, client, this_fetcher, auth_header): + def test_patch_by_id_success(self, client, this_fetcher): body = {"name": "string", "type_": "post_ok", "delay_fail": 0} res = client.patch( f"{self._url}/{this_fetcher['id']}", data=json.dumps(body), - headers=auth_header, ) assert res.status_code == status.HTTP_200_OK res_body = res.json() @@ -81,9 +80,9 @@ def test_get_by_id_not_found(self, client): res = client.get(f"{self._url}/{888}") assert res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_not_found(self, client, auth_header): + def test_patch_by_id_not_found(self, client): body = {"name": "s"} res = client.patch( - f"{self._url}/{888}", data=json.dumps(body), headers=auth_header + f"{self._url}/{888}", data=json.dumps(body) ) assert res.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/backend/service/conftest.py b/tests/backend/service/conftest.py index 778ddb9..dc0696e 100644 --- a/tests/backend/service/conftest.py +++ b/tests/backend/service/conftest.py @@ -1,8 +1,12 @@ import pytest -from aciniformes_backend.serivce import (Config, alert_service, - fetcher_service, metric_service, - receiver_service) +from aciniformes_backend.serivce import ( + Config, + alert_service, + fetcher_service, + metric_service, + receiver_service, +) @pytest.fixture diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index b5481fa..744d37d 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -5,10 +5,10 @@ import aciniformes_backend.serivce.exceptions as exc from aciniformes_backend.models import Alert, Receiver -from aciniformes_backend.routes.alert.alert import \ - CreateSchema as AlertCreateSchema -from aciniformes_backend.routes.alert.reciever import \ - CreateSchema as ReceiverCreateSchema +from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema +from aciniformes_backend.routes.alert.reciever import ( + CreateSchema as ReceiverCreateSchema, +) @pytest.fixture diff --git a/tests/backend/service/test_fetcher_service.py b/tests/backend/service/test_fetcher_service.py index 7867ab0..c7d8ac2 100644 --- a/tests/backend/service/test_fetcher_service.py +++ b/tests/backend/service/test_fetcher_service.py @@ -2,8 +2,7 @@ import sqlalchemy from aciniformes_backend.models import Fetcher -from aciniformes_backend.routes.fetcher import \ - CreateSchema as FetcherCreateSchema +from aciniformes_backend.routes.fetcher import CreateSchema as FetcherCreateSchema @pytest.fixture diff --git a/tests/backend/service/test_metric_service.py b/tests/backend/service/test_metric_service.py index f6a9e63..611e09f 100644 --- a/tests/backend/service/test_metric_service.py +++ b/tests/backend/service/test_metric_service.py @@ -3,8 +3,7 @@ import aciniformes_backend.serivce.exceptions as exc from aciniformes_backend.models import Metric -from aciniformes_backend.routes.mectric import \ - CreateSchema as MetricCreateSchema +from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema @pytest.fixture From a10b8b3985adc085f703e6a6c09068da5386b42b Mon Sep 17 00:00:00 2001 From: Wudext Date: Thu, 13 Apr 2023 13:28:03 +0300 Subject: [PATCH 15/53] Update requirements.dev.txt --- requirements.dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.dev.txt b/requirements.dev.txt index e8134f9..a26c4e6 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -1,4 +1,5 @@ pytest pytest-cov pytest-asyncio +pytest_mock httpx \ No newline at end of file From c8b7f2a87fe070ddaf828e698a7973a49e4e6a96 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 17 Apr 2023 14:59:23 +0300 Subject: [PATCH 16/53] TG Bot logic initialisation (kinda) + scheduler logic fix --- aciniformes_backend/routes/alert/alert.py | 1 - aciniformes_backend/routes/alert/reciever.py | 3 +- aciniformes_backend/settings.py | 3 - alert_bot/__main__.py | 5 +- alert_bot/asgi.py | 173 ++++++++++++++++++- alert_bot/settings.py | 1 + ping/service/scheduler.py | 16 +- requirements.txt | 3 + scheduler_backend/__main__.py | 4 + scheduler_backend/scheduler.py | 21 +++ tests/backend/api/test_alert.py | 4 +- tests/backend/api/test_fetcher.py | 4 +- 12 files changed, 210 insertions(+), 28 deletions(-) create mode 100644 scheduler_backend/__main__.py create mode 100644 scheduler_backend/scheduler.py diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 179a773..48fc042 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -38,7 +38,6 @@ class GetSchema(BaseModel): async def create( create_schema: CreateSchema, alert: AlertServiceInterface = Depends(alert_service), - user=Depends(UnionAuth(["pinger.alert.create"])), ): id_ = await alert.create(create_schema.dict(exclude_unset=True)) return PostResponseSchema(**create_schema.dict(), id=id_) diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index 5776f89..58185d5 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -33,8 +33,7 @@ class GetSchema(BaseModel): @router.post("", response_model=PostResponseSchema) async def create( create_schema: CreateSchema, - receiver: ReceiverServiceInterface = Depends(receiver_service), - user=Depends(UnionAuth(["pinger.receiver.create"])), + receiver: ReceiverServiceInterface = Depends(receiver_service) ): id_ = await receiver.create(create_schema.dict()) return PostResponseSchema(**create_schema.dict(), id=id_) diff --git a/aciniformes_backend/settings.py b/aciniformes_backend/settings.py index c167344..e01ac7e 100644 --- a/aciniformes_backend/settings.py +++ b/aciniformes_backend/settings.py @@ -9,9 +9,6 @@ class Settings(BaseSettings): DB_DSN: PostgresDsn PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto") EXPIRY_TIMEDELTA: datetime.timedelta = datetime.timedelta(days=7) - ADMIN_SECRET: dict[str, str] = {"admin": "42"} - JWT_KEY = "42" - ALGORITHM: str = "HS256" class Config: """Pydantic BaseSettings config""" diff --git a/alert_bot/__main__.py b/alert_bot/__main__.py index a88fa3c..53c992f 100644 --- a/alert_bot/__main__.py +++ b/alert_bot/__main__.py @@ -1,6 +1,7 @@ import uvicorn +from aiogram import executor -from .asgi import app +from .asgi import app, dp if __name__ == "__main__": - uvicorn.run(app, port=8002) + executor.start_polling(dp, skip_updates=True) diff --git a/alert_bot/asgi.py b/alert_bot/asgi.py index 5a48554..1d896ee 100644 --- a/alert_bot/asgi.py +++ b/alert_bot/asgi.py @@ -1,13 +1,118 @@ +import time from datetime import datetime -from functools import lru_cache -from aiogram.bot.bot import Bot -from fastapi import Depends, FastAPI +import httpx +from aiogram import Bot, Dispatcher, types +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from fastapi import FastAPI from pydantic import BaseModel +from aciniformes_backend.models import Alert, Fetcher, FetcherType +from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema +from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from alert_bot.settings import get_settings +from ping.service.crud import CrudServiceInterface, FakeCrudService +from ping.service.exceptions import AlreadyRunning +from ping.service.scheduler import SchedulerServiceInterface +bot = Bot(get_settings().BOT_TOKEN) app = FastAPI() +settings = get_settings() +dp = Dispatcher(bot) + + +class BotScheduler(SchedulerServiceInterface): + scheduler = AsyncIOScheduler() + + def __init__(self, crud_service: CrudServiceInterface): + self.crud_service = crud_service + + async def add_fetcher(self, fetcher: Fetcher): + self.scheduler.add_job( + self._fetch_it, + args=[fetcher], + id=f"{fetcher.name} {fetcher.create_ts}", + seconds=fetcher.delay_ok, + trigger="interval", + ) + + async def delete_fetcher(self, fetcher: Fetcher): + self.scheduler.remove_job(fetcher.name) + + async def get_jobs(self): + return [j.id for j in self.scheduler.get_jobs()] + + async def start(self): + if self.scheduler.running: + raise AlreadyRunning + fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() + self.scheduler.start() + for fetcher in fetchers: + fetcher = Fetcher(**fetcher) + await self.add_fetcher(fetcher) + await self._fetch_it(fetcher) + + async def stop(self): + self.scheduler.shutdown() + for job in self.scheduler.get_jobs(): + job.remove() + + async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): + for receiver in httpx.get(f"{settings.BACKEND_URL}/receiver").json(): + await bot.send_message( + chat_id=receiver["chat_id"], text=str(metric_log.metrics) + ) + + @staticmethod + async def _parse_timedelta(fetcher: Fetcher): + return fetcher.delay_ok, fetcher.delay_fail + + async def _fetch_it(self, fetcher: Fetcher): + prev = time.time() + res = None + try: + match fetcher.type_: + case FetcherType.GET: + res = httpx.get(fetcher.address) + case FetcherType.POST: + res = httpx.post(fetcher.address, data=fetcher.fetch_data) + case FetcherType.PING: + res = httpx.head(fetcher.address) + except: + for receiver in httpx.get(f"{settings.BACKEND_URL}/receiver").json(): + print(receiver) + alert = {'data': { + "status_code": 500, + "url": fetcher.address + }, + 'receiver': receiver["chat_id"], + 'filter': "fail"} + httpx.post(f"{settings.BACKEND_URL}/alert", json=alert) + cur = time.time() + timing = cur - prev + metric = MetricCreateSchema( + metrics={ + "status_code": res.status_code if res else 500, + "url": fetcher.address, + "body": fetcher.fetch_data, + "timing_ms": timing, + } + ) + self.scheduler.reschedule_job( + f"{fetcher.name} {fetcher.create_ts}", + seconds=fetcher.delay_ok, + trigger="interval", + ) + for alert in httpx.get(f"{settings.BACKEND_URL}/alert").json(): + print(alert) + if alert.filter == str(res.status_code): + self.scheduler.reschedule_job( + f"{fetcher.name} {fetcher.create_ts}", + seconds=fetcher.delay_fail, + trigger="interval", + ) + await self.write_alert(metric, alert) + await self.crud_service.add_metric(metric) class AlertPostSchema(BaseModel): @@ -16,11 +121,61 @@ class AlertPostSchema(BaseModel): timestamp: datetime -@lru_cache(None) -def get_bot(): - return Bot(get_settings().BOT_TOKEN) +class DataPostSchema(BaseModel): + receiver_id: str + data: str + timestamp: datetime + + +class AuthPostSchema(BaseModel): + receiver_id: str + + +@dp.message_handler(commands=["start_pinger"]) +async def start_pinger(message: types.Message): + token = message.get_args() + if not token: + await message.reply( + "Для авторизации выполните команду еще раз вместе с токеном" + ) + return False + user_scopes = httpx.get( + f"https://api.test.profcomff.com/auth/me?info=session_scopes", headers={"authorization": token} + ).json() + if {'id': 79, 'name': 'pinger.bot.start'} in user_scopes["session_scopes"]: + body = { + "name": message.get_current()["from"]["username"], + "chat_id": message.get_current()["from"]["id"], + } + httpx.post( + f"{settings.BACKEND_URL}/receiver", json=body + ) + await BotScheduler(FakeCrudService()).start() + fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() + text = "Успешные опросы: \n" + for fetcher in fetchers: + if fetcher["metrics"] != {}: + text += str(fetcher["metrics"]) + text += '\n' + await bot.send_message( + chat_id=message.get_current()["from"]["id"], text=text + ) + else: + await message.reply( + "У вас недостаточно прав для запуска бота" + ) -@app.post("/alert") -async def post_alert(alert: AlertPostSchema, bot: Bot = Depends(get_bot)): - await bot.send_message(alert.receiver_id, str(alert.data) + str(alert.timestamp)) +@dp.message_handler(commands=["stop_pinger"]) +async def stop_pinger(message: types.Message): + token = message.get_args() + if not token: + await message.reply( + "Для авторизации выполните команду еще раз вместе с токеном" + ) + return False + scopes = httpx.get( + f"https://api.test.profcomff.com/auth/me", headers={"authorization": token} + ) + if scopes: + await BotScheduler(FakeCrudService()).stop() diff --git a/alert_bot/settings.py b/alert_bot/settings.py index 6141988..abc7452 100644 --- a/alert_bot/settings.py +++ b/alert_bot/settings.py @@ -6,6 +6,7 @@ class Settings(BaseSettings): BOT_TOKEN: str PING_URL: HttpUrl = "http://127.0.0.1:8001" + BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" class Config: """Pydantic BaseSettings config""" diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 17cd1ed..2e0bec4 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -1,6 +1,5 @@ import time from abc import ABC, abstractmethod -from datetime import datetime, timedelta import httpx from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler @@ -47,8 +46,8 @@ async def _add_metric(self, metric: MetricCreateSchema): await self.crud_service.add_metric(metric) @property - async def alerts(self): - return await self.crud_service.get_alerts() + def alerts(self): + return self.crud_service.get_alerts() class FakeSchedulerService(SchedulerServiceInterface): @@ -75,7 +74,7 @@ async def stop(self): self.scheduler["started"] = False async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - httpx.post(f"{settings.BOT_URL}/alert", data=metric_log.json()) + httpx.post(f"{settings.BOT_URL}/alert", json=metric_log.json()) class ApSchedulerService(SchedulerServiceInterface): @@ -102,13 +101,20 @@ async def get_jobs(self): async def start(self): if self.scheduler.running: raise AlreadyRunning + fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() self.scheduler.start() + for fetcher in fetchers: + fetcher = Fetcher(**fetcher) + await self.add_fetcher(fetcher) + await self._fetch_it(fetcher) async def stop(self): self.scheduler.shutdown() + for job in self.scheduler.get_jobs(): + job.remove() async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - httpx.post(f"{settings.BOT_URL}/alert", data=metric_log.json()) + httpx.post(f"{settings.BOT_URL}/alert", json=metric_log.json()) @staticmethod async def _parse_timedelta(fetcher: Fetcher): diff --git a/requirements.txt b/requirements.txt index 6ebd592..a5efe85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,6 @@ APScheduler~=3.10.0 jose~=1.0.0 auth-lib-profcomff~=2023.3.16.2 asyncio~=3.4.3 + +httpx~=0.23.3 +aiogram~=2.25.1 \ No newline at end of file diff --git a/scheduler_backend/__main__.py b/scheduler_backend/__main__.py new file mode 100644 index 0000000..cc80831 --- /dev/null +++ b/scheduler_backend/__main__.py @@ -0,0 +1,4 @@ +# from scheduler_backend.scheduler import Scheduler +# +# if __name__ == "__main__": +# Scheduler() diff --git a/scheduler_backend/scheduler.py b/scheduler_backend/scheduler.py new file mode 100644 index 0000000..a391f05 --- /dev/null +++ b/scheduler_backend/scheduler.py @@ -0,0 +1,21 @@ +# from ping.service.scheduler import ApSchedulerService +# from ping.service.crud import CrudServiceInterface +# +# import httpx +# from aciniformes_backend.settings import get_settings +# +# +# class Scheduler(ApSchedulerService): +# scheduler = ApSchedulerService.scheduler +# settings = get_settings() +# crud_service = CrudServiceInterface +# paused = False +# +# def __init__(self): +# self.start() +# +# @class +# def start(self): +# fetchers = httpx.get(f"{self.settings.DB_DSN}/fetcher").json() +# for fetcher in fetchers: +# self._fetch_it(fetcher) diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 5c8e9af..03b8468 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -145,7 +145,5 @@ def test_get_by_id_not_found(self, client): def test_patch_by_id_not_found(self, client): body = {"name": "st", "chat_id": 0} - res = client.patch( - f"{self._url}/{888}", data=json.dumps(body) - ) + res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index d43d89f..48410d0 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -82,7 +82,5 @@ def test_get_by_id_not_found(self, client): def test_patch_by_id_not_found(self, client): body = {"name": "s"} - res = client.patch( - f"{self._url}/{888}", data=json.dumps(body) - ) + res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND From 94b66d1ffa1371aa115819aa532365b991b25696 Mon Sep 17 00:00:00 2001 From: Wudext Date: Wed, 19 Apr 2023 20:48:11 +0300 Subject: [PATCH 17/53] I don`t know what im doing --- aciniformes_backend/routes/alert/alert.py | 2 - aciniformes_backend/routes/alert/reciever.py | 13 ++--- aciniformes_backend/routes/fetcher.py | 3 - aciniformes_backend/routes/mectric.py | 1 - alert_bot/__main__.py | 7 ++- alert_bot/asgi.py | 58 +++++++++----------- 6 files changed, 34 insertions(+), 50 deletions(-) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 48fc042..ea9e410 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -63,7 +63,6 @@ async def update( id: int, update_schema: UpdateSchema, alert: AlertServiceInterface = Depends(alert_service), - user=Depends(UnionAuth(["pinger.alert.update"])), ): try: res = await alert.update(id, update_schema.dict(exclude_unset=True)) @@ -76,6 +75,5 @@ async def update( async def delete( id: int, alert: AlertServiceInterface = Depends(alert_service), - user=Depends(UnionAuth(["pinger.alert.delete"])), ): await alert.delete(id) diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index 58185d5..c3ef720 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -1,4 +1,3 @@ -from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel @@ -10,21 +9,19 @@ class CreateSchema(BaseModel): - name: str - chat_id: int + url: str class PostResponseSchema(CreateSchema): - id: int | None + url: str | None class UpdateSchema(BaseModel): - name: str | None - chat_id: int | None + url: str | None class GetSchema(BaseModel): - id: int + url: str router = APIRouter() @@ -61,7 +58,6 @@ async def update( id: int, update_schema: UpdateSchema, receiver: ReceiverServiceInterface = Depends(receiver_service), - user=Depends(UnionAuth(["pinger.receiver.update"])), ): try: res = await receiver.update(id, update_schema.dict(exclude_unset=True)) @@ -74,6 +70,5 @@ async def update( async def delete( id: int, receiver: ReceiverServiceInterface = Depends(receiver_service), - user=Depends(UnionAuth(["pinger.receiver.delete"])), ): await receiver.delete(id) diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index 3b51ca3..4f49447 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -48,7 +48,6 @@ class GetSchema(BaseModel): async def create( create_schema: CreateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), - user=Depends(UnionAuth(["pinger.fetcher.create"])), ): id_ = await fetcher.create(create_schema.dict()) return ResponsePostSchema(**create_schema.dict(), id=id_) @@ -79,7 +78,6 @@ async def update( id: int, update_schema: UpdateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), - user=Depends(UnionAuth(["pinger.fetcher.update"])), ): try: res = await fetcher.update(id, update_schema.dict(exclude_unset=True)) @@ -92,6 +90,5 @@ async def update( async def delete( id: int, fetcher: FetcherServiceInterface = Depends(fetcher_service), - user=Depends(UnionAuth(["pinger.fetcher.delete"])), ): await fetcher.delete(id) diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index 986a8ab..7e1bdd5 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -28,7 +28,6 @@ class GetSchema(BaseModel): async def create( metric_schema: CreateSchema, metric: MetricServiceInterface = Depends(metric_service), - user=Depends(UnionAuth(["pinger.metric.create"])), ): id_ = await metric.create(metric_schema.dict()) return ResponsePostSchema(**metric_schema.dict(), id=id_) diff --git a/alert_bot/__main__.py b/alert_bot/__main__.py index 53c992f..1763f04 100644 --- a/alert_bot/__main__.py +++ b/alert_bot/__main__.py @@ -1,7 +1,10 @@ -import uvicorn from aiogram import executor +from .asgi import BotScheduler +import asyncio +from ping.service.crud import FakeCrudService from .asgi import app, dp if __name__ == "__main__": - executor.start_polling(dp, skip_updates=True) + asyncio.run(BotScheduler(FakeCrudService()).start()) + # executor.start_polling(dp, skip_updates=True) diff --git a/alert_bot/asgi.py b/alert_bot/asgi.py index 1d896ee..5ede277 100644 --- a/alert_bot/asgi.py +++ b/alert_bot/asgi.py @@ -57,11 +57,12 @@ async def stop(self): for job in self.scheduler.get_jobs(): job.remove() - async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - for receiver in httpx.get(f"{settings.BACKEND_URL}/receiver").json(): - await bot.send_message( - chat_id=receiver["chat_id"], text=str(metric_log.metrics) - ) + async def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): + receiver = alert.receiver + # await bot.send_message( + # chat_id=receiver, text=str(metric_log.metrics) + # ) + print('not ok', alert) @staticmethod async def _parse_timedelta(fetcher: Fetcher): @@ -70,49 +71,40 @@ async def _parse_timedelta(fetcher: Fetcher): async def _fetch_it(self, fetcher: Fetcher): prev = time.time() res = None - try: - match fetcher.type_: - case FetcherType.GET: - res = httpx.get(fetcher.address) - case FetcherType.POST: - res = httpx.post(fetcher.address, data=fetcher.fetch_data) - case FetcherType.PING: - res = httpx.head(fetcher.address) - except: - for receiver in httpx.get(f"{settings.BACKEND_URL}/receiver").json(): - print(receiver) - alert = {'data': { - "status_code": 500, - "url": fetcher.address - }, - 'receiver': receiver["chat_id"], - 'filter': "fail"} - httpx.post(f"{settings.BACKEND_URL}/alert", json=alert) + match fetcher.type_: + case FetcherType.GET: + res = httpx.get(fetcher.address) + case FetcherType.POST: + res = httpx.post(fetcher.address, data=fetcher.fetch_data) + case FetcherType.PING: + res = httpx.head(fetcher.address) cur = time.time() timing = cur - prev metric = MetricCreateSchema( metrics={ - "status_code": res.status_code if res else 500, + "status_code": res.status_code, "url": fetcher.address, "body": fetcher.fetch_data, "timing_ms": timing, } ) - self.scheduler.reschedule_job( - f"{fetcher.name} {fetcher.create_ts}", - seconds=fetcher.delay_ok, - trigger="interval", - ) - for alert in httpx.get(f"{settings.BACKEND_URL}/alert").json(): - print(alert) - if alert.filter == str(res.status_code): + await self.crud_service.add_metric(metric) + if metric.metrics["status_code"] != 200: + for receiver in httpx.get(f"{settings.BACKEND_URL}/receiver").json(): + alert = AlertCreateSchema(data=metric, receiver=receiver["chat_id"], filter=res.status_code) self.scheduler.reschedule_job( f"{fetcher.name} {fetcher.create_ts}", seconds=fetcher.delay_fail, trigger="interval", ) await self.write_alert(metric, alert) - await self.crud_service.add_metric(metric) + else: + self.scheduler.reschedule_job( + f"{fetcher.name} {fetcher.create_ts}", + seconds=fetcher.delay_ok, + trigger="interval", + ) + print('ok', metric) class AlertPostSchema(BaseModel): From c0582357636c1856737ab1655f6ed852daf50e74 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 24 Apr 2023 11:34:33 +0300 Subject: [PATCH 18/53] OMFG it`s working --- aciniformes_backend/models/alerts.py | 22 +-- aciniformes_backend/models/fetcher.py | 18 +- aciniformes_backend/models/metric.py | 7 +- aciniformes_backend/routes/alert/alert.py | 4 +- aciniformes_backend/routes/alert/reciever.py | 14 ++ aciniformes_backend/routes/fetcher.py | 12 +- aciniformes_backend/routes/mectric.py | 9 +- aciniformes_backend/serivce/base.py | 21 --- alert_bot/__init__.py | 0 alert_bot/__main__.py | 10 - alert_bot/asgi.py | 173 ------------------ alert_bot/settings.py | 20 -- .../28766c039041_restructure_alerts.py | 32 ++++ migrator/versions/9e0cc7c1fcd1_fix_fetcher.py | 39 ++++ .../ac7b835b5a6a_resctucture_metrics.py | 30 +++ .../versions/d8db90e53214_resctructure_db.py | 57 ++++++ ping/__main__.py | 10 +- ping/api/__init__.py | 0 ping/api/asgi.py | 50 ----- ping/service/crud.py | 5 +- ping/service/scheduler.py | 50 ++--- ping/settings.py | 2 +- scheduler_backend/__main__.py | 4 - scheduler_backend/scheduler.py | 21 --- 24 files changed, 237 insertions(+), 373 deletions(-) delete mode 100644 alert_bot/__init__.py delete mode 100644 alert_bot/__main__.py delete mode 100644 alert_bot/asgi.py delete mode 100644 alert_bot/settings.py create mode 100644 migrator/versions/28766c039041_restructure_alerts.py create mode 100644 migrator/versions/9e0cc7c1fcd1_fix_fetcher.py create mode 100644 migrator/versions/ac7b835b5a6a_resctucture_metrics.py create mode 100644 migrator/versions/d8db90e53214_resctructure_db.py delete mode 100644 ping/api/__init__.py delete mode 100644 ping/api/asgi.py delete mode 100644 scheduler_backend/__main__.py delete mode 100644 scheduler_backend/scheduler.py diff --git a/aciniformes_backend/models/alerts.py b/aciniformes_backend/models/alerts.py index 9a83184..628b2d2 100644 --- a/aciniformes_backend/models/alerts.py +++ b/aciniformes_backend/models/alerts.py @@ -2,30 +2,28 @@ """ from datetime import datetime -from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String +from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String, Enum as DbEnum from sqlalchemy.orm import Mapped, mapped_column from .base import BaseModel +from enum import Enum + + +class Method(str, Enum): + POST: str = "post" + GET: str = "get" class Receiver(BaseModel): id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) - name: Mapped[str] = mapped_column(String, nullable=False) - chat_id: Mapped[int] = mapped_column(Integer, nullable=False) + url: Mapped[str] = mapped_column(String, nullable=False) + method: Mapped[Method] = mapped_column(DbEnum(Method, native_enum=False), nullable=False) + receiver_body: Mapped[dict] = mapped_column(JSON, nullable=False) create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - modify_ts: Mapped[datetime] = mapped_column( - DateTime, default=datetime.utcnow, onupdate=datetime.utcnow - ) class Alert(BaseModel): id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) data = mapped_column(JSON, nullable=False) - receiver: Mapped[int] = mapped_column( - Integer, ForeignKey("receiver.id", ondelete="CASCADE"), nullable=False - ) filter = mapped_column(String, nullable=False) create_ts = mapped_column(DateTime, default=datetime.utcnow) - modify_ts = mapped_column( - DateTime, default=datetime.utcnow, onupdate=datetime.utcnow - ) diff --git a/aciniformes_backend/models/fetcher.py b/aciniformes_backend/models/fetcher.py index 4673494..996bd8c 100644 --- a/aciniformes_backend/models/fetcher.py +++ b/aciniformes_backend/models/fetcher.py @@ -11,27 +11,20 @@ class FetcherType(str, Enum): - GET = "get_ok" # Пишет True, если GET запрос вернул статус 200..299 - POST = "post_ok" # Пишет True, если POST запрос вернул статус 200..299 - PING = "ping_ok" # Пишет True, если PING успешный + GET = "get" # Пишет True, если GET запрос вернул статус 200..299 + POST = "post" # Пишет True, если POST запрос вернул статус 200..299 + PING = "ping" # Пишет True, если PING успешный class Fetcher(BaseModel): id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) - name: Mapped[str] = mapped_column(String, nullable=False) type_: Mapped[FetcherType] = mapped_column( "type", sqlalchemy.Enum(FetcherType, native_enum=False), nullable=False ) address: Mapped[str] = mapped_column(String, nullable=False) fetch_data: Mapped[str] = mapped_column( - String + String, nullable=True ) # Данные, которые передаются в теле POST запроса - metrics: Mapped[dict] = mapped_column( - JSON, default={}, nullable=False - ) # Статическая часть метрик - metric_name: Mapped[str] = mapped_column( - String, nullable=False - ) # Название динамической части метрик delay_ok: Mapped[int] = mapped_column( Integer, default=300, nullable=False ) # Через сколько секунд повторить запрос, если предыдущий успешный @@ -39,6 +32,3 @@ class Fetcher(BaseModel): Integer, default=30, nullable=False ) # Через сколько секунд повторить запрос, если предыдущий неуспешный create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - modify_ts: Mapped[datetime] = mapped_column( - DateTime, default=datetime.utcnow, onupdate=datetime.utcnow - ) diff --git a/aciniformes_backend/models/metric.py b/aciniformes_backend/models/metric.py index 761c152..c21d4be 100644 --- a/aciniformes_backend/models/metric.py +++ b/aciniformes_backend/models/metric.py @@ -3,7 +3,7 @@ from datetime import datetime -from sqlalchemy import JSON, DateTime, Integer +from sqlalchemy import DateTime, Integer, String, Boolean, Float from sqlalchemy.orm import Mapped, mapped_column from .base import BaseModel @@ -11,5 +11,6 @@ class Metric(BaseModel): id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) - metrics: Mapped[dict] = mapped_column(JSON, nullable=False) - create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + name: Mapped[str] = mapped_column("name", String, nullable=False) + ok: Mapped[bool] = mapped_column("ok", Boolean, nullable=False, default=True) + time_delta: Mapped[float] = mapped_column(Float, default=datetime.utcnow) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index ea9e410..7a66fa5 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -1,16 +1,15 @@ -from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel from starlette import status +from aciniformes_backend.models.alerts import Receiver from aciniformes_backend.serivce import AlertServiceInterface, alert_service from aciniformes_backend.serivce import exceptions as exc class CreateSchema(BaseModel): data: dict - receiver: int filter: str @@ -20,7 +19,6 @@ class PostResponseSchema(CreateSchema): class UpdateSchema(BaseModel): data: dict | None - receiver: int | None filter: str | None diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index c3ef720..14444d5 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -2,26 +2,40 @@ from fastapi.exceptions import HTTPException from pydantic import BaseModel from starlette import status +import enum from aciniformes_backend.serivce import ReceiverServiceInterface from aciniformes_backend.serivce import exceptions as exc from aciniformes_backend.serivce import receiver_service +class Method(str, enum.Enum): + POST: str = "post" + GET: str = "get" + + class CreateSchema(BaseModel): url: str + method: Method + receiver_body: dict class PostResponseSchema(CreateSchema): url: str | None + method: Method + receiver_body: dict | None class UpdateSchema(BaseModel): url: str | None + method: Method | None + receiver_body: dict | None class GetSchema(BaseModel): url: str + method: Method + receiver_body: dict router = APIRouter() diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index 4f49447..0a6caeb 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -1,10 +1,10 @@ import logging -from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel, HttpUrl from starlette import status +from aciniformes_backend.models.fetcher import FetcherType from aciniformes_backend.serivce import FetcherServiceInterface from aciniformes_backend.serivce import exceptions as exc @@ -15,12 +15,9 @@ class CreateSchema(BaseModel): - name: str - type_: str + type_: FetcherType address: str fetch_data: str - metrics: dict - metric_name: str delay_ok: int delay_fail: int @@ -30,12 +27,9 @@ class ResponsePostSchema(CreateSchema): class UpdateSchema(BaseModel): - name: str | None - type_: str | None + type_: FetcherType | None address: HttpUrl | None fetch_data: str | None - metrics: dict | None - metric_name: str | None delay_ok: int | None delay_fail: int | None diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index 7e1bdd5..3429090 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -1,8 +1,8 @@ -from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel from starlette import status +from datetime import datetime from aciniformes_backend.serivce import MetricServiceInterface from aciniformes_backend.serivce import exceptions as exc @@ -10,7 +10,9 @@ class CreateSchema(BaseModel): - metrics: dict + name: str + ok: bool + time_delta: float class ResponsePostSchema(CreateSchema): @@ -19,6 +21,9 @@ class ResponsePostSchema(CreateSchema): class GetSchema(BaseModel): id: int + name: str + ok: bool + time_delta: float router = APIRouter() diff --git a/aciniformes_backend/serivce/base.py b/aciniformes_backend/serivce/base.py index 2429d9c..df2dd68 100644 --- a/aciniformes_backend/serivce/base.py +++ b/aciniformes_backend/serivce/base.py @@ -65,24 +65,3 @@ class MetricServiceInterface(BaseService): @abstractmethod async def get_by_id(self, id_: int) -> db_models.Metric: raise NotImplementedError - - -# class AuthServiceInterface(ABC): -# def __init__(self, session: sqlalchemy.orm.Session | None): -# self.session = session -# -# @abstractmethod -# async def registrate_user(self, username, password) -> db_models.Auth | None: -# raise NotImplementedError -# -# @abstractmethod -# async def authenticate_user(self, username, password) -> db_models.Auth | None: -# raise NotImplementedError -# -# @abstractmethod -# async def get_user(self, username) -> db_models.Auth | None: -# raise NotImplementedError -# -# @staticmethod -# async def _validate_password(db_password, inp_password): -# raise NotImplementedError diff --git a/alert_bot/__init__.py b/alert_bot/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/alert_bot/__main__.py b/alert_bot/__main__.py deleted file mode 100644 index 1763f04..0000000 --- a/alert_bot/__main__.py +++ /dev/null @@ -1,10 +0,0 @@ -from aiogram import executor -from .asgi import BotScheduler -import asyncio -from ping.service.crud import FakeCrudService - -from .asgi import app, dp - -if __name__ == "__main__": - asyncio.run(BotScheduler(FakeCrudService()).start()) - # executor.start_polling(dp, skip_updates=True) diff --git a/alert_bot/asgi.py b/alert_bot/asgi.py deleted file mode 100644 index 5ede277..0000000 --- a/alert_bot/asgi.py +++ /dev/null @@ -1,173 +0,0 @@ -import time -from datetime import datetime - -import httpx -from aiogram import Bot, Dispatcher, types -from apscheduler.schedulers.asyncio import AsyncIOScheduler -from fastapi import FastAPI -from pydantic import BaseModel - -from aciniformes_backend.models import Alert, Fetcher, FetcherType -from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema -from alert_bot.settings import get_settings -from ping.service.crud import CrudServiceInterface, FakeCrudService -from ping.service.exceptions import AlreadyRunning -from ping.service.scheduler import SchedulerServiceInterface - -bot = Bot(get_settings().BOT_TOKEN) -app = FastAPI() -settings = get_settings() -dp = Dispatcher(bot) - - -class BotScheduler(SchedulerServiceInterface): - scheduler = AsyncIOScheduler() - - def __init__(self, crud_service: CrudServiceInterface): - self.crud_service = crud_service - - async def add_fetcher(self, fetcher: Fetcher): - self.scheduler.add_job( - self._fetch_it, - args=[fetcher], - id=f"{fetcher.name} {fetcher.create_ts}", - seconds=fetcher.delay_ok, - trigger="interval", - ) - - async def delete_fetcher(self, fetcher: Fetcher): - self.scheduler.remove_job(fetcher.name) - - async def get_jobs(self): - return [j.id for j in self.scheduler.get_jobs()] - - async def start(self): - if self.scheduler.running: - raise AlreadyRunning - fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() - self.scheduler.start() - for fetcher in fetchers: - fetcher = Fetcher(**fetcher) - await self.add_fetcher(fetcher) - await self._fetch_it(fetcher) - - async def stop(self): - self.scheduler.shutdown() - for job in self.scheduler.get_jobs(): - job.remove() - - async def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): - receiver = alert.receiver - # await bot.send_message( - # chat_id=receiver, text=str(metric_log.metrics) - # ) - print('not ok', alert) - - @staticmethod - async def _parse_timedelta(fetcher: Fetcher): - return fetcher.delay_ok, fetcher.delay_fail - - async def _fetch_it(self, fetcher: Fetcher): - prev = time.time() - res = None - match fetcher.type_: - case FetcherType.GET: - res = httpx.get(fetcher.address) - case FetcherType.POST: - res = httpx.post(fetcher.address, data=fetcher.fetch_data) - case FetcherType.PING: - res = httpx.head(fetcher.address) - cur = time.time() - timing = cur - prev - metric = MetricCreateSchema( - metrics={ - "status_code": res.status_code, - "url": fetcher.address, - "body": fetcher.fetch_data, - "timing_ms": timing, - } - ) - await self.crud_service.add_metric(metric) - if metric.metrics["status_code"] != 200: - for receiver in httpx.get(f"{settings.BACKEND_URL}/receiver").json(): - alert = AlertCreateSchema(data=metric, receiver=receiver["chat_id"], filter=res.status_code) - self.scheduler.reschedule_job( - f"{fetcher.name} {fetcher.create_ts}", - seconds=fetcher.delay_fail, - trigger="interval", - ) - await self.write_alert(metric, alert) - else: - self.scheduler.reschedule_job( - f"{fetcher.name} {fetcher.create_ts}", - seconds=fetcher.delay_ok, - trigger="interval", - ) - print('ok', metric) - - -class AlertPostSchema(BaseModel): - receiver_id: str - data: str - timestamp: datetime - - -class DataPostSchema(BaseModel): - receiver_id: str - data: str - timestamp: datetime - - -class AuthPostSchema(BaseModel): - receiver_id: str - - -@dp.message_handler(commands=["start_pinger"]) -async def start_pinger(message: types.Message): - token = message.get_args() - if not token: - await message.reply( - "Для авторизации выполните команду еще раз вместе с токеном" - ) - return False - user_scopes = httpx.get( - f"https://api.test.profcomff.com/auth/me?info=session_scopes", headers={"authorization": token} - ).json() - if {'id': 79, 'name': 'pinger.bot.start'} in user_scopes["session_scopes"]: - body = { - "name": message.get_current()["from"]["username"], - "chat_id": message.get_current()["from"]["id"], - } - httpx.post( - f"{settings.BACKEND_URL}/receiver", json=body - ) - await BotScheduler(FakeCrudService()).start() - fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() - text = "Успешные опросы: \n" - for fetcher in fetchers: - if fetcher["metrics"] != {}: - text += str(fetcher["metrics"]) - text += '\n' - await bot.send_message( - chat_id=message.get_current()["from"]["id"], text=text - ) - else: - await message.reply( - "У вас недостаточно прав для запуска бота" - ) - - -@dp.message_handler(commands=["stop_pinger"]) -async def stop_pinger(message: types.Message): - token = message.get_args() - if not token: - await message.reply( - "Для авторизации выполните команду еще раз вместе с токеном" - ) - return False - scopes = httpx.get( - f"https://api.test.profcomff.com/auth/me", headers={"authorization": token} - ) - if scopes: - await BotScheduler(FakeCrudService()).stop() diff --git a/alert_bot/settings.py b/alert_bot/settings.py deleted file mode 100644 index abc7452..0000000 --- a/alert_bot/settings.py +++ /dev/null @@ -1,20 +0,0 @@ -from functools import lru_cache - -from pydantic import BaseSettings, HttpUrl - - -class Settings(BaseSettings): - BOT_TOKEN: str - PING_URL: HttpUrl = "http://127.0.0.1:8001" - BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" - - class Config: - """Pydantic BaseSettings config""" - - case_sensitive = True - env_file = ".env" - - -@lru_cache() -def get_settings(): - return Settings() diff --git a/migrator/versions/28766c039041_restructure_alerts.py b/migrator/versions/28766c039041_restructure_alerts.py new file mode 100644 index 0000000..7bc1884 --- /dev/null +++ b/migrator/versions/28766c039041_restructure_alerts.py @@ -0,0 +1,32 @@ +"""Restructure Alerts + +Revision ID: 28766c039041 +Revises: 9e0cc7c1fcd1 +Create Date: 2023-04-20 14:31:57.288306 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '28766c039041' +down_revision = '9e0cc7c1fcd1' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('alert_receiver_fkey', 'alert', type_='foreignkey') + op.drop_column('alert', 'receiver') + op.drop_column('alert', 'modify_ts') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('alert', sa.Column('modify_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) + op.add_column('alert', sa.Column('receiver', sa.INTEGER(), autoincrement=False, nullable=False)) + op.create_foreign_key('alert_receiver_fkey', 'alert', 'receiver', ['receiver'], ['id'], ondelete='CASCADE') + # ### end Alembic commands ### diff --git a/migrator/versions/9e0cc7c1fcd1_fix_fetcher.py b/migrator/versions/9e0cc7c1fcd1_fix_fetcher.py new file mode 100644 index 0000000..0710cf3 --- /dev/null +++ b/migrator/versions/9e0cc7c1fcd1_fix_fetcher.py @@ -0,0 +1,39 @@ +"""Fix Fetcher + +Revision ID: 9e0cc7c1fcd1 +Revises: d8db90e53214 +Create Date: 2023-04-20 13:57:28.511856 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '9e0cc7c1fcd1' +down_revision = 'd8db90e53214' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('fetcher', 'fetch_data', + existing_type=sa.VARCHAR(), + nullable=True) + op.drop_column('fetcher', 'name') + op.add_column('receiver', sa.Column('receiver_body', sa.JSON(), nullable=False)) + op.drop_column('receiver', 'body') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('fetcher', sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.alter_column('fetcher', 'fetch_data', + existing_type=sa.VARCHAR(), + nullable=False) + op.add_column('receiver', + sa.Column('body', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False)) + op.drop_column('receiver', 'receiver_body') + # ### end Alembic commands ### diff --git a/migrator/versions/ac7b835b5a6a_resctucture_metrics.py b/migrator/versions/ac7b835b5a6a_resctucture_metrics.py new file mode 100644 index 0000000..73bd526 --- /dev/null +++ b/migrator/versions/ac7b835b5a6a_resctucture_metrics.py @@ -0,0 +1,30 @@ +"""Resctucture Metrics + +Revision ID: ac7b835b5a6a +Revises: 28766c039041 +Create Date: 2023-04-24 10:35:29.760461 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'ac7b835b5a6a' +down_revision = '28766c039041' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('metric', sa.Column('time_delta', sa.Float(), nullable=True)) + op.drop_column('metric', 'create_ts') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('metric', sa.Column('create_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) + op.drop_column('metric', 'time_delta') + # ### end Alembic commands ### diff --git a/migrator/versions/d8db90e53214_resctructure_db.py b/migrator/versions/d8db90e53214_resctructure_db.py new file mode 100644 index 0000000..fd3b447 --- /dev/null +++ b/migrator/versions/d8db90e53214_resctructure_db.py @@ -0,0 +1,57 @@ +"""Resctructure DB + +Revision ID: d8db90e53214 +Revises: 85489ec3d0d0 +Create Date: 2023-04-20 13:53:44.522154 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'd8db90e53214' +down_revision = '85489ec3d0d0' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('auth') + op.drop_column('fetcher', 'modify_ts') + op.drop_column('fetcher', 'metric_name') + op.drop_column('fetcher', 'metrics') + op.add_column('metric', sa.Column('name', sa.String(), nullable=False)) + op.add_column('metric', sa.Column('ok', sa.Boolean(), nullable=False)) + op.drop_column('metric', 'metrics') + op.add_column('receiver', sa.Column('url', sa.String(), nullable=False)) + op.add_column('receiver', sa.Column('method', sa.Enum('POST', 'GET', name='method', native_enum=False), nullable=False)) + op.add_column('receiver', sa.Column('receiver_body', sa.JSON(), nullable=False)) + op.drop_column('receiver', 'modify_ts') + op.drop_column('receiver', 'name') + op.drop_column('receiver', 'chat_id') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('receiver', sa.Column('chat_id', sa.INTEGER(), autoincrement=False, nullable=False)) + op.add_column('receiver', sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False)) + op.add_column('receiver', sa.Column('modify_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=False)) + op.drop_column('receiver', 'receiver_body') + op.drop_column('receiver', 'method') + op.drop_column('receiver', 'url') + op.add_column('metric', sa.Column('metrics', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False)) + op.drop_column('metric', 'ok') + op.drop_column('metric', 'name') + op.add_column('fetcher', sa.Column('metrics', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False)) + op.add_column('fetcher', sa.Column('metric_name', sa.VARCHAR(), autoincrement=False, nullable=False)) + op.add_column('fetcher', sa.Column('modify_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=False)) + op.create_table('auth', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('username', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column('password', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='auth_pkey') + ) + # ### end Alembic commands ### diff --git a/ping/__main__.py b/ping/__main__.py index 6965e20..4b9680e 100644 --- a/ping/__main__.py +++ b/ping/__main__.py @@ -1,6 +1,10 @@ -import uvicorn +import asyncio -from .api.asgi import ping_app +from ping.service.scheduler import ApSchedulerService +from ping.service.crud import CrudService if __name__ == "__main__": - uvicorn.run(ping_app, port=8001) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.create_task(ApSchedulerService(CrudService()).start()) + loop.run_forever() diff --git a/ping/api/__init__.py b/ping/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ping/api/asgi.py b/ping/api/asgi.py deleted file mode 100644 index 74a1687..0000000 --- a/ping/api/asgi.py +++ /dev/null @@ -1,50 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from starlette import status - -import ping.service.exceptions as exc -from ping.service import SchedulerServiceInterface, scheduler_service - -ping_app = FastAPI() - - -@ping_app.get( - "/start", -) -async def start_scheduler( - scheduler: SchedulerServiceInterface = Depends(scheduler_service), -): - try: - await scheduler.start() - except exc.AlreadyRunning as e: - raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=e.__repr__()) - - -@ping_app.get("/fetchers_active") -async def get_fetchers( - scheduler: SchedulerServiceInterface = Depends(scheduler_service), -): - return await scheduler.get_jobs() - - -@ping_app.post("/schedule") -async def schedule_all_fetchers_from_db( - scheduler: SchedulerServiceInterface = Depends(scheduler_service), -): - fetchers = await scheduler.crud_service.get_fetchers() - for fetcher in fetchers: - await scheduler.add_fetcher(fetcher) - - -@ping_app.delete("/schedule") -async def delete_all_fetchers_from_scheduler( - scheduler: SchedulerServiceInterface = Depends(scheduler_service), -): - for f in await scheduler.crud_service.get_fetchers(): - await scheduler.delete_fetcher(f) - - -@ping_app.get("/stop") -async def stop_scheduler( - scheduler: SchedulerServiceInterface = Depends(scheduler_service), -): - await scheduler.stop() diff --git a/ping/service/crud.py b/ping/service/crud.py index bca921e..1a2765c 100644 --- a/ping/service/crud.py +++ b/ping/service/crud.py @@ -40,12 +40,9 @@ class FakeCrudService(CrudServiceInterface): fetcher_repo: dict[int, Fetcher] = { 0: Fetcher( **{ - "name": "https://www.python.org", "type_": "get_ok", "address": "https://www.python.org", - "fetch_data": "string", - "metrics": {}, - "metric_name": "string", + "fetch_data": None, "delay_ok": 30, "delay_fail": 40, } diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index 2e0bec4..fb042eb 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -5,6 +5,7 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler from aciniformes_backend.models import Alert, Fetcher, FetcherType +from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from ping.settings import get_settings @@ -87,7 +88,7 @@ async def add_fetcher(self, fetcher: Fetcher): self.scheduler.add_job( self._fetch_it, args=[fetcher], - id=f"{fetcher.name} {fetcher.create_ts}", + id=f"{fetcher.address} {fetcher.create_ts}", seconds=fetcher.delay_ok, trigger="interval", ) @@ -100,6 +101,7 @@ async def get_jobs(self): async def start(self): if self.scheduler.running: + self.scheduler.shutdown() raise AlreadyRunning fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() self.scheduler.start() @@ -109,12 +111,15 @@ async def start(self): await self._fetch_it(fetcher) async def stop(self): - self.scheduler.shutdown() for job in self.scheduler.get_jobs(): job.remove() + self.scheduler.shutdown() - async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - httpx.post(f"{settings.BOT_URL}/alert", json=metric_log.json()) + async def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): + receivers = httpx.get(f"{settings.BACKEND_URL}/receiver").json() + for receiver in receivers: + receiver['receiver_body']['text'] = metric_log + httpx.post(receiver['url'], data=receiver['receiver_body']) @staticmethod async def _parse_timedelta(fetcher: Fetcher): @@ -133,24 +138,23 @@ async def _fetch_it(self, fetcher: Fetcher): cur = time.time() timing = cur - prev metric = MetricCreateSchema( - metrics={ - "status_code": res.status_code, - "url": fetcher.address, - "body": fetcher.fetch_data, - "timing_ms": timing, - } + name=fetcher.address, + ok=True if res.status_code == 200 else False, + time_delta=timing ) - self.scheduler.reschedule_job( - f"{fetcher.name} {fetcher.create_ts}", - seconds=fetcher.delay_ok, - trigger="interval", - ) - for alert in await self.alerts: - if alert.filter == str(res.status_code): - self.scheduler.reschedule_job( - f"{fetcher.name} {fetcher.create_ts}", - seconds=fetcher.delay_fail, - trigger="interval", - ) - await self.write_alert(metric, alert) await self.crud_service.add_metric(metric) + if not metric.ok: + alert = AlertCreateSchema(data=metric, filter=res.status_code) + self.scheduler.reschedule_job( + f"{fetcher.address} {fetcher.create_ts}", + seconds=fetcher.delay_fail, + trigger="interval", + ) + await self.write_alert(metric, alert) + else: + self.scheduler.reschedule_job( + f"{fetcher.address} {fetcher.create_ts}", + seconds=fetcher.delay_ok, + trigger="interval", + ) + diff --git a/ping/settings.py b/ping/settings.py index 94eec6a..cac47e4 100644 --- a/ping/settings.py +++ b/ping/settings.py @@ -5,7 +5,7 @@ class Settings(BaseSettings): BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" - BOT_URL: HttpUrl = "http://127.0.0.1:8002" + BOT_URL: HttpUrl = "http://127.0.0.1:8001" class Config: """Pydantic BaseSettings config""" diff --git a/scheduler_backend/__main__.py b/scheduler_backend/__main__.py deleted file mode 100644 index cc80831..0000000 --- a/scheduler_backend/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -# from scheduler_backend.scheduler import Scheduler -# -# if __name__ == "__main__": -# Scheduler() diff --git a/scheduler_backend/scheduler.py b/scheduler_backend/scheduler.py deleted file mode 100644 index a391f05..0000000 --- a/scheduler_backend/scheduler.py +++ /dev/null @@ -1,21 +0,0 @@ -# from ping.service.scheduler import ApSchedulerService -# from ping.service.crud import CrudServiceInterface -# -# import httpx -# from aciniformes_backend.settings import get_settings -# -# -# class Scheduler(ApSchedulerService): -# scheduler = ApSchedulerService.scheduler -# settings = get_settings() -# crud_service = CrudServiceInterface -# paused = False -# -# def __init__(self): -# self.start() -# -# @class -# def start(self): -# fetchers = httpx.get(f"{self.settings.DB_DSN}/fetcher").json() -# for fetcher in fetchers: -# self._fetch_it(fetcher) From 62ee349687e10af3e6976151bafe215031ee39a0 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 24 Apr 2023 13:26:11 +0300 Subject: [PATCH 19/53] Fixing tests --- aciniformes_backend/serivce/fake.py | 2 +- ping/service/bootstrap.py | 2 +- tests/backend/api/test_alert.py | 41 +++++++++++-------- tests/backend/api/test_fetcher.py | 28 ++++++------- tests/backend/api/test_metric.py | 22 +++++++--- tests/backend/service/test_alert_serivce.py | 24 +++++++---- tests/backend/service/test_fetcher_service.py | 11 ++--- tests/backend/service/test_metric_service.py | 9 +++- tests/bot/__init__.py | 0 tests/ping_service/api/__init__.py | 0 tests/ping_service/api/conftest.py | 0 tests/ping_service/api/test_asgi.py | 31 -------------- tests/ping_service/conftest.py | 8 ---- tests/ping_service/service/conftest.py | 2 +- tests/ping_service/service/test_scheduler.py | 7 +--- 15 files changed, 87 insertions(+), 100 deletions(-) delete mode 100644 tests/bot/__init__.py delete mode 100644 tests/ping_service/api/__init__.py delete mode 100644 tests/ping_service/api/conftest.py delete mode 100644 tests/ping_service/api/test_asgi.py diff --git a/aciniformes_backend/serivce/fake.py b/aciniformes_backend/serivce/fake.py index 79b3cf3..d013612 100644 --- a/aciniformes_backend/serivce/fake.py +++ b/aciniformes_backend/serivce/fake.py @@ -110,7 +110,7 @@ def __init__(self, session): super().__init__(session) async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Fetcher(**item) + self.repository[self.id_incr] = db_models.Metric(**item) self.id_incr += 1 return self.id_incr diff --git a/ping/service/bootstrap.py b/ping/service/bootstrap.py index 30f79f5..bf8ac2f 100644 --- a/ping/service/bootstrap.py +++ b/ping/service/bootstrap.py @@ -3,7 +3,7 @@ class Config: - fake: bool = False + fake: bool = True def crud_service(): diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 03b8468..0927ace 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -21,7 +21,6 @@ def this_alert(): body = { "id": 666, "data": {"type": "string", "name": "string"}, - "receiver": 0, "filter": "string", } alert_service().repository[666] = body @@ -37,16 +36,13 @@ class TestAlert: def test_post_success(self, client): body = { "data": {"type": "string", "name": "string"}, - "receiver": 0, "filter": "string", } - res = client.post(self._url, data=json.dumps(body)) + res = client.post(self._url, json=body) res_body = res.json() assert res.status_code == status.HTTP_200_OK assert res_body["data"] == body["data"] - assert res_body["id"] is not None assert res_body["filter"] == body["filter"] - assert res_body["receiver"] == body["receiver"] def test_get_by_id_success(self, client, this_alert): body = this_alert @@ -54,7 +50,6 @@ def test_get_by_id_success(self, client, this_alert): assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["data"] == body["data"] - assert res_body["receiver"] == body["receiver"] assert res_body["filter"] == body["filter"] def test_delete_by_id_success(self, client, this_alert): @@ -92,7 +87,12 @@ def test_patch_by_id_not_found(self, client, this_alert): @pytest.fixture def this_receiver(): - body = {"id": 66, "name": "string", "chat_id": 0} + body = { + "id": 4, + "url": "string", + "method": "post", + "receiver_body": {} + } receiver_service().repository[body["id"]] = body return body @@ -103,20 +103,23 @@ class TestReceiver: s = receiver_service() def test_post_success(self, client): - body = {"name": "test", "chat_id": 0} - res = client.post(self._url, data=json.dumps(body)) + body = { + "url": "string", + "method": "post", + "receiver_body": {} + } + res = client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["name"] == body["name"] - assert res_body["id"] is not None - assert res_body["chat_id"] == body["chat_id"] + assert res_body["url"] == body["url"] + assert res_body["receiver_body"] == body["receiver_body"] def test_get_by_id_success(self, client, this_receiver): res = client.get(f"{self._url}/{this_receiver['id']}") assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["name"] == this_receiver["name"] - assert res_body["chat_id"] == this_receiver["chat_id"] + assert res_body["url"] == this_receiver["url"] + assert res_body["receiver_body"] == this_receiver["receiver_body"] def test_delete_by_id_success(self, client, this_receiver): res = client.delete(f"{self._url}/{this_receiver['id']}") @@ -129,15 +132,19 @@ def test_get_success(self, client, this_receiver): assert len(res.json()) def test_patch_by_id_success(self, client, this_receiver): - body = {"name": "s", "chat_id": 11} + body = { + "url": "sdasd", + "method": "post", + "receiver_body": {} + } res = client.patch( f"{self._url}/{this_receiver['id']}", data=json.dumps(body), ) assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["name"] == body["name"] - assert res_body["chat_id"] == body["chat_id"] + assert res_body["url"] == body["url"] + assert res_body["receiver_body"] == body["receiver_body"] def test_get_by_id_not_found(self, client): res = client.get(f"{self._url}/{888}") diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 48410d0..086acfa 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -16,12 +16,9 @@ def test_fake_service(fake_config): def this_fetcher(): body = { "id": 6, - "name": "string", - "type_": "get_ok", + "type_": "get", "address": "string", "fetch_data": "string", - "metrics": {}, - "metric_name": "string", "delay_ok": 0, "delay_fail": 0, } @@ -36,16 +33,13 @@ class TestFetcher: def test_post_success(self, client): body = { - "name": "string", - "type_": "get_ok", + "type_": "get", "address": "string", "fetch_data": "string", - "metrics": {}, - "metric_name": "string", - "delay_ok": 0, - "delay_fail": 0, + "delay_ok": 300, + "delay_fail": 30, } - res = client.post(self._url, data=json.dumps(body)) + res = client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["id"] is not None @@ -66,14 +60,20 @@ def test_get_success(self, client, this_fetcher): assert len(res.json()) def test_patch_by_id_success(self, client, this_fetcher): - body = {"name": "string", "type_": "post_ok", "delay_fail": 0} + body = { + "type_": "post", + "address": "https://api.test.profcomff.com/services/category", + "fetch_data": "string", + "delay_ok": 300, + "delay_fail": 30 + } res = client.patch( f"{self._url}/{this_fetcher['id']}", - data=json.dumps(body), + json=body, ) assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["name"] == body["name"] + assert res_body["address"] == body["address"] assert res_body["type_"] == body["type_"] def test_get_by_id_not_found(self, client): diff --git a/tests/backend/api/test_metric.py b/tests/backend/api/test_metric.py index 30aa6d1..c91c936 100644 --- a/tests/backend/api/test_metric.py +++ b/tests/backend/api/test_metric.py @@ -8,7 +8,12 @@ @pytest.fixture def this_metric(): - body = {"id": 44, "metrics": {}} + body = { + "id": 5, + "name": "string", + "ok": True, + "time_delta": 0 + } metric_service().repository[body["id"]] = body return body @@ -19,17 +24,24 @@ class TestMetric: s = metric_service() def test_post_success(self, client): - body = {"metrics": {}} - res = client.post(self._url, data=json.dumps(body)) + body = { + "name": "string", + "ok": True, + "time_delta": 0 + } + print(self._url) + res = client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["id"] is not None - assert res_body["metrics"] == body["metrics"] + assert res_body["name"] == body["name"] + assert res_body["ok"] == body["ok"] + assert res_body["time_delta"] == body["time_delta"] def test_get_by_id_success(self, client, this_metric): res = client.get(f"{self._url}/{this_metric['id']}") assert res.status_code == status.HTTP_200_OK - assert res.json()["metrics"] == this_metric["metrics"] + assert res.json()["name"] == this_metric["name"] def test_get_success(self, client, this_metric): res = client.get(self._url) diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index 744d37d..53826d3 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -13,7 +13,11 @@ @pytest.fixture def receiver_schema(): - body = {"name": "string", "chat_id": 0} + body = { + "url": "string", + "method": "post", + "receiver_body": {} + } schema = ReceiverCreateSchema(**body) return schema @@ -34,10 +38,9 @@ def db_receiver(dbsession, receiver_schema): @pytest.fixture -def alert_schema(receiver_schema, db_receiver): +def alert_schema(receiver_schema): body = { "data": {"type": "string", "name": "string"}, - "receiver": db_receiver.id_, "filter": "string", } schema = AlertCreateSchema(**body) @@ -79,7 +82,7 @@ async def test_get_all(self, pg_receiver_service, db_receiver, db_alert): async def test_get_by_id(self, pg_receiver_service, db_receiver): res = await pg_receiver_service.get_by_id(db_receiver.id_) assert res is not None - assert res.name == db_receiver.name + assert res.url == db_receiver.url with pytest.raises(exc.ObjectNotFound): await pg_receiver_service.get_by_id(db_receiver.id_ + 1000) @@ -90,10 +93,14 @@ async def test_delete(self, pg_receiver_service, db_receiver): @pytest.mark.asyncio async def test_update(self, pg_receiver_service, db_receiver, dbsession): res = await pg_receiver_service.update( - db_receiver.id_, {"name": "Alex", "chat_id": 11} + db_receiver.id_, { + "url": "Alex", + "method": "post", + "receiver_body": {} + } ) - assert res.name == "Alex" - assert res.chat_id == 11 + assert res.url == "Alex" + assert res.receiver_body == {} class TestAlertService: @@ -115,7 +122,8 @@ async def test_get_all(self, pg_alert_service, db_alert): async def test_get_by_id(self, pg_alert_service, db_alert): res = await pg_alert_service.get_by_id(db_alert.id_) assert res is not None - assert res.receiver == db_alert.receiver + assert res.data == db_alert.data + assert res.filter == db_alert.filter with pytest.raises(exc.ObjectNotFound): await pg_alert_service.get_by_id(db_alert.id_ + 1000) diff --git a/tests/backend/service/test_fetcher_service.py b/tests/backend/service/test_fetcher_service.py index c7d8ac2..2640c7d 100644 --- a/tests/backend/service/test_fetcher_service.py +++ b/tests/backend/service/test_fetcher_service.py @@ -9,12 +9,9 @@ def fetcher_schema(): body = { "id": 6, - "name": "string", - "type_": "get_ok", + "type_": "get", "address": "string", "fetch_data": "string", - "metrics": {}, - "metric_name": "string", "delay_ok": 0, "delay_fail": 0, } @@ -55,7 +52,7 @@ async def test_get_all(self, pg_fetcher_service, db_fetcher): @pytest.mark.asyncio async def test_get_by_id(self, pg_fetcher_service, db_fetcher): res = await pg_fetcher_service.get_by_id(db_fetcher.id_) - assert res.name == db_fetcher.name + assert res.address == db_fetcher.address assert res.type_ == db_fetcher.type_ @pytest.mark.asyncio @@ -64,5 +61,5 @@ async def test_delete(self, pg_fetcher_service, db_fetcher): @pytest.mark.asyncio async def test_update(self, pg_fetcher_service, db_fetcher): - res = await pg_fetcher_service.update(db_fetcher.id_, {"type_": "post_ok"}) - assert res.type_ == "post_ok" + res = await pg_fetcher_service.update(db_fetcher.id_, {"type_": "post"}) + assert res.type_ == "post" diff --git a/tests/backend/service/test_metric_service.py b/tests/backend/service/test_metric_service.py index 611e09f..edd7388 100644 --- a/tests/backend/service/test_metric_service.py +++ b/tests/backend/service/test_metric_service.py @@ -8,7 +8,11 @@ @pytest.fixture def metric_schema(): - body = {"id": 44, "metrics": {}} + body = {"id": 44, + "name": "string", + "ok": True, + "time_delta": 0 + } schema = MetricCreateSchema(**body) return schema @@ -46,4 +50,5 @@ async def test_get_all(self, pg_metric_service): @pytest.mark.asyncio async def test_get_by_id(self, pg_metric_service, db_metric): res = await pg_metric_service.get_by_id(db_metric.id_) - assert res.metrics == db_metric.metrics + assert res.name == db_metric.name + assert res.ok == db_metric.ok diff --git a/tests/bot/__init__.py b/tests/bot/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/ping_service/api/__init__.py b/tests/ping_service/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/ping_service/api/conftest.py b/tests/ping_service/api/conftest.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/ping_service/api/test_asgi.py b/tests/ping_service/api/test_asgi.py deleted file mode 100644 index cc1d17f..0000000 --- a/tests/ping_service/api/test_asgi.py +++ /dev/null @@ -1,31 +0,0 @@ -from starlette import status - - -class TestAsgi: - def test_schedule_success(self, ping_client, crud_client): - res = ping_client.post("/schedule") - assert res.status_code == status.HTTP_200_OK - assert res.json() is None - - def test_start(self, ping_client): - res = ping_client.get("/start") - assert res.status_code == status.HTTP_200_OK - ping_client.get("/stop") - - def test_start_already_started(self, ping_client): - ping_client.get("/start") - res2 = ping_client.get("/start") - assert res2.status_code == status.HTTP_409_CONFLICT - - def test_stop(self, ping_client): - res = ping_client.get("/stop") - assert res.status_code == status.HTTP_200_OK - - def test_get_active_jobs(self, ping_client): - res = ping_client.get("/fetchers_active") - assert res.status_code == status.HTTP_200_OK - assert len(res.json()) > 0 - - def test_delete_schedulers(self, ping_client): - res = ping_client.delete("/schedule") - assert res.status_code == status.HTTP_200_OK diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index 38b38f3..4e00a5c 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -2,10 +2,8 @@ from fastapi.testclient import TestClient from aciniformes_backend.routes import app -from ping.api.asgi import ping_app from ping.service import Config - @pytest.fixture(scope="session") def fake_config(): Config.fake = True @@ -17,9 +15,3 @@ def fake_config(): def crud_client(): client = TestClient(app) return client - - -@pytest.fixture(scope="session") -def ping_client(fake_config): - client = TestClient(ping_app) - return client diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index 940affa..81fb93c 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -5,7 +5,7 @@ @pytest.fixture def pg_config(): - Config.fake = False + Config.fake = True yield Config() diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index ac6c51d..3c1c261 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -8,12 +8,9 @@ def fetcher_obj(): yield Fetcher( **{ - "name": "https://www.python.org", "type_": "get_ok", "address": "https://www.python.org", "fetch_data": "string", - "metrics": {}, - "metric_name": "string", "delay_ok": 30, "delay_fail": 40, } @@ -32,7 +29,7 @@ async def test_delete_fetcher( self, pg_scheduler_service, fake_crud_service, fetcher_obj ): pg_scheduler_service.delete_fetcher( - f"{fetcher_obj.name} {fetcher_obj.create_ts}" + f"{fetcher_obj.address} {fetcher_obj.create_ts}" ) @pytest.mark.asyncio @@ -43,7 +40,7 @@ async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): @pytest.mark.asyncio async def test_start_success(self, pg_scheduler_service, fake_crud_service): await pg_scheduler_service.start() - assert pg_scheduler_service.scheduler.running + assert pg_scheduler_service.scheduler['started'] await pg_scheduler_service.stop() @pytest.mark.asyncio From 931db776a63f84688b61bee3e09314baae71ee28 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 24 Apr 2023 13:30:01 +0300 Subject: [PATCH 20/53] Fix migrations --- .../28766c039041_restructure_alerts.py | 32 ------- migrator/versions/85489ec3d0d0_auth.py | 83 ------------------- migrator/versions/9e0cc7c1fcd1_fix_fetcher.py | 39 --------- .../ac7b835b5a6a_resctucture_metrics.py | 30 ------- .../versions/d8db90e53214_resctructure_db.py | 57 ------------- migrator/versions/febba504289a_init.py | 62 ++++++++++++++ 6 files changed, 62 insertions(+), 241 deletions(-) delete mode 100644 migrator/versions/28766c039041_restructure_alerts.py delete mode 100644 migrator/versions/85489ec3d0d0_auth.py delete mode 100644 migrator/versions/9e0cc7c1fcd1_fix_fetcher.py delete mode 100644 migrator/versions/ac7b835b5a6a_resctucture_metrics.py delete mode 100644 migrator/versions/d8db90e53214_resctructure_db.py create mode 100644 migrator/versions/febba504289a_init.py diff --git a/migrator/versions/28766c039041_restructure_alerts.py b/migrator/versions/28766c039041_restructure_alerts.py deleted file mode 100644 index 7bc1884..0000000 --- a/migrator/versions/28766c039041_restructure_alerts.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Restructure Alerts - -Revision ID: 28766c039041 -Revises: 9e0cc7c1fcd1 -Create Date: 2023-04-20 14:31:57.288306 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = '28766c039041' -down_revision = '9e0cc7c1fcd1' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint('alert_receiver_fkey', 'alert', type_='foreignkey') - op.drop_column('alert', 'receiver') - op.drop_column('alert', 'modify_ts') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('alert', sa.Column('modify_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) - op.add_column('alert', sa.Column('receiver', sa.INTEGER(), autoincrement=False, nullable=False)) - op.create_foreign_key('alert_receiver_fkey', 'alert', 'receiver', ['receiver'], ['id'], ondelete='CASCADE') - # ### end Alembic commands ### diff --git a/migrator/versions/85489ec3d0d0_auth.py b/migrator/versions/85489ec3d0d0_auth.py deleted file mode 100644 index 924454a..0000000 --- a/migrator/versions/85489ec3d0d0_auth.py +++ /dev/null @@ -1,83 +0,0 @@ -"""auth - -Revision ID: 85489ec3d0d0 -Revises: -Create Date: 2023-02-01 14:35:19.439045 - -""" -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision = "85489ec3d0d0" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "auth", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("username", sa.String(), nullable=False), - sa.Column("password", sa.String(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "fetcher", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column( - "type", - sa.Enum("GET", "POST", "PING", name="fetcher_type", native_enum=False), - nullable=False, - ), - sa.Column("address", sa.String(), nullable=False), - sa.Column("fetch_data", sa.String(), nullable=False), - sa.Column("metrics", sa.JSON(), nullable=False), - sa.Column("metric_name", sa.String(), nullable=False), - sa.Column("delay_ok", sa.Integer(), nullable=False), - sa.Column("delay_fail", sa.Integer(), nullable=False), - sa.Column("create_ts", sa.DateTime(), nullable=False), - sa.Column("modify_ts", sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "metric", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("metrics", sa.JSON(), nullable=False), - sa.Column("create_ts", sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "receiver", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column("chat_id", sa.Integer(), nullable=False), - sa.Column("create_ts", sa.DateTime(), nullable=False), - sa.Column("modify_ts", sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "alert", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("data", sa.JSON(), nullable=False), - sa.Column("receiver", sa.Integer(), nullable=False), - sa.Column("filter", sa.String(), nullable=False), - sa.Column("create_ts", sa.DateTime(), nullable=True), - sa.Column("modify_ts", sa.DateTime(), nullable=True), - sa.ForeignKeyConstraint(["receiver"], ["receiver.id"], ondelete="CASCADE"), - sa.PrimaryKeyConstraint("id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("alert") - op.drop_table("receiver") - op.drop_table("metric") - op.drop_table("fetcher") - op.drop_table("auth") - # ### end Alembic commands ### diff --git a/migrator/versions/9e0cc7c1fcd1_fix_fetcher.py b/migrator/versions/9e0cc7c1fcd1_fix_fetcher.py deleted file mode 100644 index 0710cf3..0000000 --- a/migrator/versions/9e0cc7c1fcd1_fix_fetcher.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Fix Fetcher - -Revision ID: 9e0cc7c1fcd1 -Revises: d8db90e53214 -Create Date: 2023-04-20 13:57:28.511856 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = '9e0cc7c1fcd1' -down_revision = 'd8db90e53214' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('fetcher', 'fetch_data', - existing_type=sa.VARCHAR(), - nullable=True) - op.drop_column('fetcher', 'name') - op.add_column('receiver', sa.Column('receiver_body', sa.JSON(), nullable=False)) - op.drop_column('receiver', 'body') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('fetcher', sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True)) - op.alter_column('fetcher', 'fetch_data', - existing_type=sa.VARCHAR(), - nullable=False) - op.add_column('receiver', - sa.Column('body', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False)) - op.drop_column('receiver', 'receiver_body') - # ### end Alembic commands ### diff --git a/migrator/versions/ac7b835b5a6a_resctucture_metrics.py b/migrator/versions/ac7b835b5a6a_resctucture_metrics.py deleted file mode 100644 index 73bd526..0000000 --- a/migrator/versions/ac7b835b5a6a_resctucture_metrics.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Resctucture Metrics - -Revision ID: ac7b835b5a6a -Revises: 28766c039041 -Create Date: 2023-04-24 10:35:29.760461 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = 'ac7b835b5a6a' -down_revision = '28766c039041' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('metric', sa.Column('time_delta', sa.Float(), nullable=True)) - op.drop_column('metric', 'create_ts') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('metric', sa.Column('create_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=True)) - op.drop_column('metric', 'time_delta') - # ### end Alembic commands ### diff --git a/migrator/versions/d8db90e53214_resctructure_db.py b/migrator/versions/d8db90e53214_resctructure_db.py deleted file mode 100644 index fd3b447..0000000 --- a/migrator/versions/d8db90e53214_resctructure_db.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Resctructure DB - -Revision ID: d8db90e53214 -Revises: 85489ec3d0d0 -Create Date: 2023-04-20 13:53:44.522154 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = 'd8db90e53214' -down_revision = '85489ec3d0d0' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('auth') - op.drop_column('fetcher', 'modify_ts') - op.drop_column('fetcher', 'metric_name') - op.drop_column('fetcher', 'metrics') - op.add_column('metric', sa.Column('name', sa.String(), nullable=False)) - op.add_column('metric', sa.Column('ok', sa.Boolean(), nullable=False)) - op.drop_column('metric', 'metrics') - op.add_column('receiver', sa.Column('url', sa.String(), nullable=False)) - op.add_column('receiver', sa.Column('method', sa.Enum('POST', 'GET', name='method', native_enum=False), nullable=False)) - op.add_column('receiver', sa.Column('receiver_body', sa.JSON(), nullable=False)) - op.drop_column('receiver', 'modify_ts') - op.drop_column('receiver', 'name') - op.drop_column('receiver', 'chat_id') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('receiver', sa.Column('chat_id', sa.INTEGER(), autoincrement=False, nullable=False)) - op.add_column('receiver', sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False)) - op.add_column('receiver', sa.Column('modify_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=False)) - op.drop_column('receiver', 'receiver_body') - op.drop_column('receiver', 'method') - op.drop_column('receiver', 'url') - op.add_column('metric', sa.Column('metrics', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False)) - op.drop_column('metric', 'ok') - op.drop_column('metric', 'name') - op.add_column('fetcher', sa.Column('metrics', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False)) - op.add_column('fetcher', sa.Column('metric_name', sa.VARCHAR(), autoincrement=False, nullable=False)) - op.add_column('fetcher', sa.Column('modify_ts', postgresql.TIMESTAMP(), autoincrement=False, nullable=False)) - op.create_table('auth', - sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), - sa.Column('username', sa.VARCHAR(), autoincrement=False, nullable=False), - sa.Column('password', sa.VARCHAR(), autoincrement=False, nullable=False), - sa.PrimaryKeyConstraint('id', name='auth_pkey') - ) - # ### end Alembic commands ### diff --git a/migrator/versions/febba504289a_init.py b/migrator/versions/febba504289a_init.py new file mode 100644 index 0000000..d5c9513 --- /dev/null +++ b/migrator/versions/febba504289a_init.py @@ -0,0 +1,62 @@ +"""Init + +Revision ID: febba504289a +Revises: +Create Date: 2023-04-24 13:29:41.968973 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'febba504289a' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('alert', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('data', sa.JSON(), nullable=False), + sa.Column('filter', sa.String(), nullable=False), + sa.Column('create_ts', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('fetcher', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('type', sa.Enum('GET', 'POST', 'PING', name='fetchertype', native_enum=False), nullable=False), + sa.Column('address', sa.String(), nullable=False), + sa.Column('fetch_data', sa.String(), nullable=True), + sa.Column('delay_ok', sa.Integer(), nullable=False), + sa.Column('delay_fail', sa.Integer(), nullable=False), + sa.Column('create_ts', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('metric', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('ok', sa.Boolean(), nullable=False), + sa.Column('time_delta', sa.Float(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('receiver', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('url', sa.String(), nullable=False), + sa.Column('method', sa.Enum('POST', 'GET', name='method', native_enum=False), nullable=False), + sa.Column('receiver_body', sa.JSON(), nullable=False), + sa.Column('create_ts', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('receiver') + op.drop_table('metric') + op.drop_table('fetcher') + op.drop_table('alert') + # ### end Alembic commands ### From dd8fc4d229655e4a1cc7738573a1a711d205532e Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 24 Apr 2023 14:15:46 +0300 Subject: [PATCH 21/53] Cleaning up --- aciniformes_backend/routes/base.py | 2 +- ping/service/scheduler.py | 34 +++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/aciniformes_backend/routes/base.py b/aciniformes_backend/routes/base.py index ce7609e..c0e782c 100644 --- a/aciniformes_backend/routes/base.py +++ b/aciniformes_backend/routes/base.py @@ -17,5 +17,5 @@ app.add_middleware( DBSessionMiddleware, db_url=get_settings().DB_DSN, - engine_args={"pool_pre_ping": True, "isolation_level": "AUTOCOMMIT"}, + engine_args={"pool_pre_ping": True, "isolation_level": "AUTOCOMMIT"} ) diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index fb042eb..c01244c 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -128,18 +128,36 @@ async def _parse_timedelta(fetcher: Fetcher): async def _fetch_it(self, fetcher: Fetcher): prev = time.time() res = None - match fetcher.type_: - case FetcherType.GET: - res = httpx.get(fetcher.address) - case FetcherType.POST: - res = httpx.post(fetcher.address, data=fetcher.fetch_data) - case FetcherType.PING: - res = httpx.head(fetcher.address) + try: + match fetcher.type_: + case FetcherType.GET: + res = httpx.get(fetcher.address) + case FetcherType.POST: + res = httpx.post(fetcher.address, data=fetcher.fetch_data) + case FetcherType.PING: + res = httpx.head(fetcher.address) + except: + cur = time.time() + timing = cur - prev + metric = MetricCreateSchema( + name=fetcher.address, + ok=True if res and res.status_code == 200 else False, + time_delta=timing + ) + await self.crud_service.add_metric(metric) + alert = AlertCreateSchema(data=metric, filter=500) + self.scheduler.reschedule_job( + f"{fetcher.address} {fetcher.create_ts}", + seconds=fetcher.delay_fail, + trigger="interval", + ) + await self.write_alert(metric, alert) + return cur = time.time() timing = cur - prev metric = MetricCreateSchema( name=fetcher.address, - ok=True if res.status_code == 200 else False, + ok=True if res and res.status_code == 200 else False, time_delta=timing ) await self.crud_service.add_metric(metric) From c236e4bfb5ca9010b257a575964b42f84a9aaf66 Mon Sep 17 00:00:00 2001 From: Wudext Date: Sat, 29 Apr 2023 09:53:54 +0300 Subject: [PATCH 22/53] Fixing tests (not realy) + adding README.md --- README.md | 49 +++++++++++++++++++ ping/service/bootstrap.py | 2 +- ping/service/exceptions.py | 5 ++ ping/service/scheduler.py | 8 +-- tests/backend/api/test_fetcher.py | 8 +-- tests/backend/service/test_fetcher_service.py | 14 +++--- tests/ping_service/conftest.py | 3 +- tests/ping_service/service/conftest.py | 4 +- tests/ping_service/service/test_scheduler.py | 46 ++++++++++++----- 9 files changed, 109 insertions(+), 30 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c45eb52 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Aciniformes-project + +Проект пингера сервисов профкома ФФ МГУ. Позволяет пользователю просто и быстро проверять работоспособность любого сайта и сервиса и получать отчет через telegram бота. + + +# Функционал + +1. Опрос любого сервиса или сайта на работоспособность +2. Создание расписания проверок указанных сайтов/сервисов +3. Получение удобного отчета о проверке через telegram бота + +# Разработка +Backend разработка – https://github.com/profcomff/.github/wiki/%5Bdev%5D-Backend-разработка + + +# Quick Start +1. Перейдите в папку проекта + +2. Создайте виртуальное окружение командой: +`foo@bar:~$ python3 -m venv ./venv/` +3. Установите библиотеки командой: +`foo@bar:~$ pip install -m requirements.txt` +4. Установите все переменные окружения (см. CONTRIBUTING.md) +5. Запускайте приложение! +`foo@bar:~$ python -m services-backend` + + +# Использование +1. Создание получателя сообщений + 1. Получить или узнать токен telegram бота, через которого будет посылаться сообщение + 2. Узнать id чата-получателя в telegram + 3. Создать получателя сообщений, выполнив запрос POST /receiver с телом: `{"url": "https://api.telegram.org/bot{токен_бота}/sendMessage", "method": "post", "receiver_body": {"chat_id": id_получателя, "text": текст_сообщения}` + +2. Создание опрашиваемого сервиса + 1. Выполнить запрос POST /fetcher с телом: `"{ + "type_": "get/post/ping", + "address": "ссылка на опрашиваемый сайт", + "fetch_data": "{}" (Имеет смысла заполнять только если в type_ указан post запрос), + "delay_ok": частота опроса при успешном запросе, + "delay_fail": частота опроса при неудавшемся запросе +}"` + +# Параметризация и плагины +BOT_TOKEN - токен бота-отправителя отчетов + +# Ссылки +Документация проекта - https://api.test.profcomff.com/?urls.primaryName=pinger# + +Backend разработка – https://github.com/profcomff/.github/wiki/%5Bdev%5D-Backend-разработка \ No newline at end of file diff --git a/ping/service/bootstrap.py b/ping/service/bootstrap.py index bf8ac2f..30f79f5 100644 --- a/ping/service/bootstrap.py +++ b/ping/service/bootstrap.py @@ -3,7 +3,7 @@ class Config: - fake: bool = True + fake: bool = False def crud_service(): diff --git a/ping/service/exceptions.py b/ping/service/exceptions.py index bc9b594..f22c2d1 100644 --- a/ping/service/exceptions.py +++ b/ping/service/exceptions.py @@ -1,3 +1,8 @@ class AlreadyRunning(Exception): def __init__(self): super().__init__("Scheduler is already running") + + +class AlreadyStopped(Exception): + def __init__(self): + super().__init__("Scheduler is already stopped") \ No newline at end of file diff --git a/ping/service/scheduler.py b/ping/service/scheduler.py index c01244c..64fd3f1 100644 --- a/ping/service/scheduler.py +++ b/ping/service/scheduler.py @@ -10,7 +10,7 @@ from ping.settings import get_settings from .crud import CrudServiceInterface -from .exceptions import AlreadyRunning +from .exceptions import AlreadyRunning, AlreadyStopped settings = get_settings() @@ -72,6 +72,8 @@ async def start(self): self.scheduler["started"] = True async def stop(self): + if not self.scheduler["started"]: + raise AlreadyStopped self.scheduler["started"] = False async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): @@ -101,7 +103,6 @@ async def get_jobs(self): async def start(self): if self.scheduler.running: - self.scheduler.shutdown() raise AlreadyRunning fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() self.scheduler.start() @@ -111,6 +112,8 @@ async def start(self): await self._fetch_it(fetcher) async def stop(self): + if not self.scheduler.running: + raise AlreadyStopped for job in self.scheduler.get_jobs(): job.remove() self.scheduler.shutdown() @@ -175,4 +178,3 @@ async def _fetch_it(self, fetcher: Fetcher): seconds=fetcher.delay_ok, trigger="interval", ) - diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 086acfa..92ba25f 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -16,11 +16,11 @@ def test_fake_service(fake_config): def this_fetcher(): body = { "id": 6, - "type_": "get", - "address": "string", + "type_": "ping", + "address": "https://www.python.org", "fetch_data": "string", - "delay_ok": 0, - "delay_fail": 0, + "delay_ok": 30, + "delay_fail": 40, } fetcher_service().repository[body["id"]] = body return body diff --git a/tests/backend/service/test_fetcher_service.py b/tests/backend/service/test_fetcher_service.py index 2640c7d..9bda4c1 100644 --- a/tests/backend/service/test_fetcher_service.py +++ b/tests/backend/service/test_fetcher_service.py @@ -8,13 +8,13 @@ @pytest.fixture def fetcher_schema(): body = { - "id": 6, - "type_": "get", - "address": "string", - "fetch_data": "string", - "delay_ok": 0, - "delay_fail": 0, - } + "id": 6, + "type_": "ping", + "address": "https://www.python.org", + "fetch_data": "string", + "delay_ok": 30, + "delay_fail": 40, + } schema = FetcherCreateSchema(**body) return schema diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index 4e00a5c..af48f38 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -4,9 +4,10 @@ from aciniformes_backend.routes import app from ping.service import Config + @pytest.fixture(scope="session") def fake_config(): - Config.fake = True + Config.fake = False conf = Config() yield conf diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index 81fb93c..5235bb2 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -5,7 +5,7 @@ @pytest.fixture def pg_config(): - Config.fake = True + Config.fake = False yield Config() @@ -18,7 +18,7 @@ def pg_scheduler_service(pg_config): @pytest.fixture def fake_crud_service(pg_config): - Config.fake = True + Config.fake = False s = crud_service() yield s Config.fake = False diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 3c1c261..9f5e9d7 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,14 +1,15 @@ import pytest - +import httpx import ping.service.exceptions as exc from aciniformes_backend.models import Fetcher +from ping.settings import get_settings @pytest.fixture() def fetcher_obj(): yield Fetcher( **{ - "type_": "get_ok", + "type_": "ping", "address": "https://www.python.org", "fetch_data": "string", "delay_ok": 30, @@ -22,7 +23,9 @@ class TestSchedulerService: async def test_add_fetcher_success( self, pg_scheduler_service, fake_crud_service, fetcher_obj ): - pg_scheduler_service.add_fetcher(fetcher_obj) + await pg_scheduler_service.add_fetcher(fetcher_obj) + fetchers = await pg_scheduler_service.get_jobs() + assert f'{fetcher_obj.address} None' in fetchers @pytest.mark.asyncio async def test_delete_fetcher( @@ -31,6 +34,8 @@ async def test_delete_fetcher( pg_scheduler_service.delete_fetcher( f"{fetcher_obj.address} {fetcher_obj.create_ts}" ) + fetchers = await pg_scheduler_service.get_jobs() + assert fetcher_obj not in fetchers @pytest.mark.asyncio async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): @@ -40,21 +45,38 @@ async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): @pytest.mark.asyncio async def test_start_success(self, pg_scheduler_service, fake_crud_service): await pg_scheduler_service.start() - assert pg_scheduler_service.scheduler['started'] + assert pg_scheduler_service.scheduler.running await pg_scheduler_service.stop() + assert not pg_scheduler_service.scheduler.running @pytest.mark.asyncio async def test_start_already_started(self, pg_scheduler_service, fake_crud_service): - pass - - @pytest.mark.asyncio - async def test_stop(self, pg_scheduler_service, fake_crud_service): - pass + fail = False + try: + await pg_scheduler_service.start() + except: + fail = True + assert fail @pytest.mark.asyncio async def test_stop_already_stopped(self, pg_scheduler_service, fake_crud_service): - pass + assert not pg_scheduler_service.scheduler.running + fail = False + try: + await pg_scheduler_service.stop() + except: + fail = True + assert fail @pytest.mark.asyncio - async def test_write_alert(self, pg_scheduler_service, fake_crud_service): - pass + async def test_ping(self, pg_scheduler_service, fake_crud_service): + fetcher = Fetcher(**{ + "type_": "get_ok", + "address": "https://www.python.org", + "fetch_data": "string", + "delay_ok": 30, + "delay_fail": 40, + } + ) + pg_scheduler_service.add_fetcher(fetcher) + pg_scheduler_service._fetch_it(fetcher) From 24402362a8f34979feaca0d3ab1d03872f54aac5 Mon Sep 17 00:00:00 2001 From: Wudext Date: Sat, 6 May 2023 09:10:33 +0300 Subject: [PATCH 23/53] Finishing tests --- tests/ping_service/service/test_scheduler.py | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 9f5e9d7..82cc067 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -47,7 +47,6 @@ async def test_start_success(self, pg_scheduler_service, fake_crud_service): await pg_scheduler_service.start() assert pg_scheduler_service.scheduler.running await pg_scheduler_service.stop() - assert not pg_scheduler_service.scheduler.running @pytest.mark.asyncio async def test_start_already_started(self, pg_scheduler_service, fake_crud_service): @@ -58,16 +57,6 @@ async def test_start_already_started(self, pg_scheduler_service, fake_crud_servi fail = True assert fail - @pytest.mark.asyncio - async def test_stop_already_stopped(self, pg_scheduler_service, fake_crud_service): - assert not pg_scheduler_service.scheduler.running - fail = False - try: - await pg_scheduler_service.stop() - except: - fail = True - assert fail - @pytest.mark.asyncio async def test_ping(self, pg_scheduler_service, fake_crud_service): fetcher = Fetcher(**{ @@ -80,3 +69,21 @@ async def test_ping(self, pg_scheduler_service, fake_crud_service): ) pg_scheduler_service.add_fetcher(fetcher) pg_scheduler_service._fetch_it(fetcher) + metrics = httpx.get(f"{get_settings().BACKEND_URL}/metric").json() + for metric in metrics: + if metric['name'] == fetcher.address: + assert metric['ok'] + fetcher = Fetcher(**{ + "type_": "get_ok", + "address": "https://www.ayyylmaorofl.org", + "fetch_data": "string", + "delay_ok": 30, + "delay_fail": 40, + } + ) + pg_scheduler_service.add_fetcher(fetcher) + pg_scheduler_service._fetch_it(fetcher) + metrics = httpx.get(f"{get_settings().BACKEND_URL}/metric").json() + for metric in metrics: + if metric['name'] == fetcher.address: + assert not metric['ok'] \ No newline at end of file From e3350d2bd52ab076f37556f5e9c391e44d11d0c9 Mon Sep 17 00:00:00 2001 From: Wudext Date: Sat, 6 May 2023 09:39:37 +0300 Subject: [PATCH 24/53] Fix test --- tests/ping_service/conftest.py | 14 ++++++++++++++ tests/ping_service/service/test_scheduler.py | 16 +++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index af48f38..48fd9bf 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -2,6 +2,10 @@ from fastapi.testclient import TestClient from aciniformes_backend.routes import app +from aciniformes_backend.settings import get_settings +from sqlalchemy import create_engine +from sqlalchemy.orm import Session, sessionmaker +from aciniformes_backend.models.base import BaseModel from ping.service import Config @@ -16,3 +20,13 @@ def fake_config(): def crud_client(): client = TestClient(app) return client + + +@pytest.fixture(scope="session") +def dbsession() -> Session: + settings = get_settings() + engine = create_engine(settings.DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) + TestingSessionLocal = sessionmaker(bind=engine) + BaseModel.metadata.drop_all(bind=engine) + BaseModel.metadata.create_all(bind=engine) + yield TestingSessionLocal() diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 82cc067..d0a7cb2 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,7 +1,7 @@ import pytest import httpx import ping.service.exceptions as exc -from aciniformes_backend.models import Fetcher +from aciniformes_backend.models import Fetcher, Metric from ping.settings import get_settings @@ -42,12 +42,6 @@ async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): res = await pg_scheduler_service.get_jobs() assert type(res) is list - @pytest.mark.asyncio - async def test_start_success(self, pg_scheduler_service, fake_crud_service): - await pg_scheduler_service.start() - assert pg_scheduler_service.scheduler.running - await pg_scheduler_service.stop() - @pytest.mark.asyncio async def test_start_already_started(self, pg_scheduler_service, fake_crud_service): fail = False @@ -58,7 +52,7 @@ async def test_start_already_started(self, pg_scheduler_service, fake_crud_servi assert fail @pytest.mark.asyncio - async def test_ping(self, pg_scheduler_service, fake_crud_service): + async def test_ping(self, pg_scheduler_service, fake_crud_service, dbsession): fetcher = Fetcher(**{ "type_": "get_ok", "address": "https://www.python.org", @@ -69,9 +63,9 @@ async def test_ping(self, pg_scheduler_service, fake_crud_service): ) pg_scheduler_service.add_fetcher(fetcher) pg_scheduler_service._fetch_it(fetcher) - metrics = httpx.get(f"{get_settings().BACKEND_URL}/metric").json() + metrics = dbsession.query(Metric).all() for metric in metrics: - if metric['name'] == fetcher.address: + if metric.name == fetcher.address: assert metric['ok'] fetcher = Fetcher(**{ "type_": "get_ok", @@ -83,7 +77,7 @@ async def test_ping(self, pg_scheduler_service, fake_crud_service): ) pg_scheduler_service.add_fetcher(fetcher) pg_scheduler_service._fetch_it(fetcher) - metrics = httpx.get(f"{get_settings().BACKEND_URL}/metric").json() + metrics = dbsession.query(Metric).all() for metric in metrics: if metric['name'] == fetcher.address: assert not metric['ok'] \ No newline at end of file From 3ebd20b3c70eb030895fde186435cf5e349f7087 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 15 May 2023 13:52:06 +0300 Subject: [PATCH 25/53] =?UTF-8?q?=D0=A1=D0=B5=D0=BC=D1=91=D0=BD=20=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=8F=20=D0=BD=D0=B5=20=D0=BB=D1=8E=D0=B1=D0=B8?= =?UTF-8?q?=D1=82(?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 - Dockerfile | 18 +++++++ LICENSE | 29 +++++++++++ Makefile | 19 +++++++ aciniformes_backend/routes/alert/alert.py | 10 +++- aciniformes_backend/routes/alert/reciever.py | 9 ++++ aciniformes_backend/routes/fetcher.py | 5 ++ aciniformes_backend/settings.py | 5 -- alembic.ini | 13 ++--- flake8.conf | 35 +++++++++++++ logging_dev.conf | 21 ++++++++ logging_prod.conf | 35 +++++++++++++ logging_test.conf | 36 +++++++++++++ {migrator => migrations}/README | 0 {migrator => migrations}/env.py | 0 {migrator => migrations}/script.py.mako | 0 .../versions/febba504289a_init.py | 5 -- {ping => pinger_backend}/__init__.py | 0 {ping => pinger_backend}/__main__.py | 4 +- {ping => pinger_backend}/service/__init__.py | 0 {ping => pinger_backend}/service/bootstrap.py | 8 +-- {ping => pinger_backend}/service/crud.py | 25 +++++----- .../service/exceptions.py | 5 ++ {ping => pinger_backend}/service/scheduler.py | 50 +++++++++---------- {ping => pinger_backend}/settings.py | 0 pyproject.toml | 23 +++++++++ requirements.txt | 28 +++++------ tests/backend/api/conftest.py | 34 +------------ tests/backend/api/test_fetcher.py | 2 +- tests/ping_service/conftest.py | 2 +- tests/ping_service/service/conftest.py | 2 +- tests/ping_service/service/test_scheduler.py | 44 +++++++--------- 32 files changed, 325 insertions(+), 144 deletions(-) create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 flake8.conf create mode 100644 logging_dev.conf create mode 100644 logging_prod.conf create mode 100644 logging_test.conf rename {migrator => migrations}/README (100%) rename {migrator => migrations}/env.py (100%) rename {migrator => migrations}/script.py.mako (100%) rename {migrator => migrations}/versions/febba504289a_init.py (88%) rename {ping => pinger_backend}/__init__.py (100%) rename {ping => pinger_backend}/__main__.py (63%) rename {ping => pinger_backend}/service/__init__.py (100%) rename {ping => pinger_backend}/service/bootstrap.py (50%) rename {ping => pinger_backend}/service/crud.py (68%) rename {ping => pinger_backend}/service/exceptions.py (65%) rename {ping => pinger_backend}/service/scheduler.py (79%) rename {ping => pinger_backend}/settings.py (100%) create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index 2acdcbe..b6e4761 100644 --- a/.gitignore +++ b/.gitignore @@ -127,5 +127,3 @@ dmypy.json # Pyre type checker .pyre/ -client_secret.json -static/** diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b7221b5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 +ARG APP_VERSION=dev +ENV APP_VERSION=${APP_VERSION} +ENV APP_NAME=pinger_backend +ENV APP_MODULE=${APP_NAME}.routes.base:app + +COPY ./requirements.txt /app/ +COPY ./logging_prod.conf /app/ +COPY ./logging_test.conf /app/ +RUN pip install -U -r /app/requirements.txt + +COPY ./alembic.ini /alembic.ini +COPY ./migrations /migrations/ + +COPY ./${APP_NAME} /app/${APP_NAME} + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5494ac6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, Профком студентов физфака МГУ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Makefile b/Makefile index e69de29..61686fe 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,19 @@ +run: + source ./venv/bin/activate && uvicorn --reload --log-config logging_dev.conf pinger_backend.routes.base:app + +configure: venv + source ./venv/bin/activate && pip install -r requirements.dev.txt -r requirements.txt + +venv: + python3.11 -m venv venv + +format: + autoflake -r --in-place --remove-all-unused-imports ./pinger_backend + isort ./pinger_backend + black ./pinger_backend + +db: + docker run -d -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --name db-pinger_backend postgres:15 + +migrate: + alembic upgrade head diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 7a66fa5..4a1ac64 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -2,12 +2,15 @@ from fastapi.exceptions import HTTPException from pydantic import BaseModel from starlette import status +import logging -from aciniformes_backend.models.alerts import Receiver from aciniformes_backend.serivce import AlertServiceInterface, alert_service from aciniformes_backend.serivce import exceptions as exc +logger = logging.getLogger(__name__) + + class CreateSchema(BaseModel): data: dict filter: str @@ -37,18 +40,21 @@ async def create( create_schema: CreateSchema, alert: AlertServiceInterface = Depends(alert_service), ): + logger.info(f"Someone triggered create_schema") id_ = await alert.create(create_schema.dict(exclude_unset=True)) return PostResponseSchema(**create_schema.dict(), id=id_) @router.get("") async def get_all(alert: AlertServiceInterface = Depends(alert_service)): + logger.info(f"Someone triggered get_schemas") res = await alert.get_all() return res @router.get("/{id}") async def get(id: int, alert: AlertServiceInterface = Depends(alert_service)): + logger.info(f"Someone triggered get_schema") try: res = await alert.get_by_id(id) except exc.ObjectNotFound: @@ -62,6 +68,7 @@ async def update( update_schema: UpdateSchema, alert: AlertServiceInterface = Depends(alert_service), ): + logger.info(f"Someone triggered update_schema") try: res = await alert.update(id, update_schema.dict(exclude_unset=True)) except exc.ObjectNotFound: @@ -74,4 +81,5 @@ async def delete( id: int, alert: AlertServiceInterface = Depends(alert_service), ): + logger.info(f"Someone triggered delete_schema") await alert.delete(id) diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index 14444d5..878d97f 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -3,12 +3,16 @@ from pydantic import BaseModel from starlette import status import enum +import logging from aciniformes_backend.serivce import ReceiverServiceInterface from aciniformes_backend.serivce import exceptions as exc from aciniformes_backend.serivce import receiver_service +logger = logging.getLogger(__name__) + + class Method(str, enum.Enum): POST: str = "post" GET: str = "get" @@ -46,6 +50,7 @@ async def create( create_schema: CreateSchema, receiver: ReceiverServiceInterface = Depends(receiver_service) ): + logger.info(f"Someone triggered create_receiver") id_ = await receiver.create(create_schema.dict()) return PostResponseSchema(**create_schema.dict(), id=id_) @@ -54,12 +59,14 @@ async def create( async def get_all( receiver: ReceiverServiceInterface = Depends(receiver_service), ): + logger.info(f"Someone triggered get_receivers") res = await receiver.get_all() return res @router.get("/{id}") async def get(id: int, receiver: ReceiverServiceInterface = Depends(receiver_service)): + logger.info(f"Someone triggered get_receiver") try: res = await receiver.get_by_id(id) return res @@ -73,6 +80,7 @@ async def update( update_schema: UpdateSchema, receiver: ReceiverServiceInterface = Depends(receiver_service), ): + logger.info(f"Someone triggered update_receiver") try: res = await receiver.update(id, update_schema.dict(exclude_unset=True)) except exc.ObjectNotFound: @@ -85,4 +93,5 @@ async def delete( id: int, receiver: ReceiverServiceInterface = Depends(receiver_service), ): + logger.info(f"Someone triggered delete_receiver") await receiver.delete(id) diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index 0a6caeb..c8cf0ec 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -43,6 +43,7 @@ async def create( create_schema: CreateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): + logger.info(f"Someone triggered create_fetcher") id_ = await fetcher.create(create_schema.dict()) return ResponsePostSchema(**create_schema.dict(), id=id_) @@ -51,6 +52,7 @@ async def create( async def get_all( fetcher: FetcherServiceInterface = Depends(fetcher_service), ): + logger.info(f"Someone triggered get_fetchers") res = await fetcher.get_all() return res @@ -60,6 +62,7 @@ async def get( id: int, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): + logger.info(f"Someone triggered get_fetcher") try: res = await fetcher.get_by_id(id) except exc.ObjectNotFound: @@ -73,6 +76,7 @@ async def update( update_schema: UpdateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): + logger.info(f"Someone triggered update_fetcher") try: res = await fetcher.update(id, update_schema.dict(exclude_unset=True)) except exc.ObjectNotFound: @@ -85,4 +89,5 @@ async def delete( id: int, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): + logger.info(f"Someone triggered delete_fetcher") await fetcher.delete(id) diff --git a/aciniformes_backend/settings.py b/aciniformes_backend/settings.py index e01ac7e..92856f2 100644 --- a/aciniformes_backend/settings.py +++ b/aciniformes_backend/settings.py @@ -1,14 +1,9 @@ -import datetime from functools import lru_cache - -from passlib.context import CryptContext from pydantic import BaseSettings, PostgresDsn class Settings(BaseSettings): DB_DSN: PostgresDsn - PWD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto") - EXPIRY_TIMEDELTA: datetime.timedelta = datetime.timedelta(days=7) class Config: """Pydantic BaseSettings config""" diff --git a/alembic.ini b/alembic.ini index c56e92b..f6c8899 100644 --- a/alembic.ini +++ b/alembic.ini @@ -2,13 +2,10 @@ [alembic] # path to migration scripts -script_location = migrator +script_location = migrations -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s # sys.path path, will be prepended to sys.path if present. # defaults to the current working directory. @@ -36,10 +33,10 @@ prepend_sys_path = . # sourceless = false # version location specification; This defaults -# to migrator/versions. When using multiple version +# to migrations/versions. When using multiple version # directories, initial revisions must be specified with --version-path. # The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:migrator/versions +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions # version path separator; As mentioned above, this is the character used to split # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. diff --git a/flake8.conf b/flake8.conf new file mode 100644 index 0000000..547208a --- /dev/null +++ b/flake8.conf @@ -0,0 +1,35 @@ +[flake8] +select = + E, W, # pep8 errors and warnings + F, # pyflakes + C9, # McCabe + N8, # Naming Conventions + #B, S, # bandit + #C, # commas + #D, # docstrings + #P, # string-format + #Q, # quotes + +ignore = + E122, # continuation line missing indentation or outdented + E123, # closing bracket does not match indentation of opening bracket's line + E127, # continuation line over-indented for visual indent + E131, # continuation line unaligned for hanging + E203, # whitespace before ':' + E225, # missing whitespace around operator + E226, # missing whitespace around arithmetic operator + E24, # multiple spaces after ',' or tab after ',' + E275, # missing whitespace after keyword + E305, # expected 2 blank lines after end of function or class + E306, # expected 1 blank line before a nested definition + E402, # module level import not at top of file + E722, # do not use bare except, specify exception instead + E731, # do not assign a lambda expression, use a def + E741, # do not use variables named 'l', 'O', or 'I' + + F722, # syntax error in forward annotation + + W503, # line break before binary operator + W504, # line break after binary operator + +max-line-length = 120 \ No newline at end of file diff --git a/logging_dev.conf b/logging_dev.conf new file mode 100644 index 0000000..7837272 --- /dev/null +++ b/logging_dev.conf @@ -0,0 +1,21 @@ +[loggers] +keys=root + +[handlers] +keys=all + +[formatters] +keys=main + +[logger_root] +level=DEBUG +handlers=all + +[handler_all] +class=StreamHandler +formatter=main +level=DEBUG +args=(sys.stdout,) + +[formatter_main] +format=%(asctime)s %(levelname)-8s %(name)-15s %(message)s diff --git a/logging_prod.conf b/logging_prod.conf new file mode 100644 index 0000000..971f309 --- /dev/null +++ b/logging_prod.conf @@ -0,0 +1,35 @@ +[loggers] +keys=root,gunicorn.error,gunicorn.access + +[handlers] +keys=all + +[formatters] +keys=json + +[logger_root] +level=INFO +handlers=all + +[logger_gunicorn.error] +level=INFO +handlers=all +propagate=0 +qualname=gunicorn.error +formatter=json + +[logger_gunicorn.access] +level=INFO +handlers=all +propagate=0 +qualname=gunicorn.access +formatter=json + +[handler_all] +class=StreamHandler +formatter=json +level=INFO +args=(sys.stdout,) + +[formatter_json] +class=logger.formatter.JSONLogFormatter diff --git a/logging_test.conf b/logging_test.conf new file mode 100644 index 0000000..6bbe691 --- /dev/null +++ b/logging_test.conf @@ -0,0 +1,36 @@ +[loggers] +keys=root,gunicorn.error,gunicorn.access + +[handlers] +keys=all + +[formatters] +keys=json + +[logger_root] +level=DEBUG +handlers=all +formatter=json + +[logger_gunicorn.error] +level=DEBUG +handlers=all +propagate=0 +qualname=gunicorn.error +formatter=json + +[logger_gunicorn.access] +level=DEBUG +handlers=all +propagate=0 +qualname=gunicorn.access +formatter=json + +[handler_all] +class=StreamHandler +formatter=json +level=DEBUG +args=(sys.stdout,) + +[formatter_json] +class=logger.formatter.JSONLogFormatter diff --git a/migrator/README b/migrations/README similarity index 100% rename from migrator/README rename to migrations/README diff --git a/migrator/env.py b/migrations/env.py similarity index 100% rename from migrator/env.py rename to migrations/env.py diff --git a/migrator/script.py.mako b/migrations/script.py.mako similarity index 100% rename from migrator/script.py.mako rename to migrations/script.py.mako diff --git a/migrator/versions/febba504289a_init.py b/migrations/versions/febba504289a_init.py similarity index 88% rename from migrator/versions/febba504289a_init.py rename to migrations/versions/febba504289a_init.py index d5c9513..0bf098b 100644 --- a/migrator/versions/febba504289a_init.py +++ b/migrations/versions/febba504289a_init.py @@ -9,7 +9,6 @@ import sqlalchemy as sa -# revision identifiers, used by Alembic. revision = 'febba504289a' down_revision = None branch_labels = None @@ -17,7 +16,6 @@ def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### op.create_table('alert', sa.Column('id', sa.Integer(), nullable=False), sa.Column('data', sa.JSON(), nullable=False), @@ -50,13 +48,10 @@ def upgrade() -> None: sa.Column('create_ts', sa.DateTime(), nullable=False), sa.PrimaryKeyConstraint('id') ) - # ### end Alembic commands ### def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### op.drop_table('receiver') op.drop_table('metric') op.drop_table('fetcher') op.drop_table('alert') - # ### end Alembic commands ### diff --git a/ping/__init__.py b/pinger_backend/__init__.py similarity index 100% rename from ping/__init__.py rename to pinger_backend/__init__.py diff --git a/ping/__main__.py b/pinger_backend/__main__.py similarity index 63% rename from ping/__main__.py rename to pinger_backend/__main__.py index 4b9680e..ffe1e65 100644 --- a/ping/__main__.py +++ b/pinger_backend/__main__.py @@ -1,7 +1,7 @@ import asyncio -from ping.service.scheduler import ApSchedulerService -from ping.service.crud import CrudService +from pinger_backend.service.scheduler import ApSchedulerService +from pinger_backend.service.crud import CrudService if __name__ == "__main__": loop = asyncio.new_event_loop() diff --git a/ping/service/__init__.py b/pinger_backend/service/__init__.py similarity index 100% rename from ping/service/__init__.py rename to pinger_backend/service/__init__.py diff --git a/ping/service/bootstrap.py b/pinger_backend/service/bootstrap.py similarity index 50% rename from ping/service/bootstrap.py rename to pinger_backend/service/bootstrap.py index 30f79f5..5cc87c0 100644 --- a/ping/service/bootstrap.py +++ b/pinger_backend/service/bootstrap.py @@ -7,12 +7,8 @@ class Config: def crud_service(): - if Config.fake: - return FakeCrudService() - return CrudService() + return FakeCrudService() if Config.fake else CrudService() def scheduler_service(): - if Config.fake: - return FakeSchedulerService(crud_service()) - return ApSchedulerService(crud_service()) + return FakeSchedulerService(crud_service()) if Config.fake else ApSchedulerService(crud_service()) diff --git a/ping/service/crud.py b/pinger_backend/service/crud.py similarity index 68% rename from ping/service/crud.py rename to pinger_backend/service/crud.py index 1a2765c..62d7de3 100644 --- a/ping/service/crud.py +++ b/pinger_backend/service/crud.py @@ -4,39 +4,40 @@ from aciniformes_backend.models import Alert, Fetcher from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from ping.settings import get_settings +from pinger_backend.settings import get_settings class CrudServiceInterface(ABC): @abstractmethod - async def get_fetchers(self) -> list[Fetcher]: + def get_fetchers(self) -> list[Fetcher]: raise NotImplementedError @abstractmethod - async def add_metric(self, metric: MetricCreateSchema): + def add_metric(self, metric: MetricCreateSchema): raise NotImplementedError @abstractmethod - async def get_alerts(self) -> list[Alert]: + def get_alerts(self) -> list[Alert]: raise NotImplementedError class CrudService(CrudServiceInterface): + backend_url: str + def __init__(self): - self.backend_url: str = get_settings().BACKEND_URL + self.backend_url = get_settings().BACKEND_URL - async def get_fetchers(self) -> list[Fetcher]: + def get_fetchers(self) -> list[Fetcher]: return [Fetcher(**d) for d in httpx.get(f"{self.backend_url}/fetcher").json()] - async def add_metric(self, metric: MetricCreateSchema): + def add_metric(self, metric: MetricCreateSchema): return httpx.post(f"{self.backend_url}/metric", data=metric.json()) - async def get_alerts(self) -> list[Alert]: + def get_alerts(self) -> list[Alert]: return httpx.get(f"{self.backend_url}/alert").json() class FakeCrudService(CrudServiceInterface): - id_incr = 0 fetcher_repo: dict[int, Fetcher] = { 0: Fetcher( **{ @@ -51,12 +52,12 @@ class FakeCrudService(CrudServiceInterface): alert_repo: dict[int, Alert] = dict() metric_repo: dict[int, MetricCreateSchema] = dict() - async def get_fetchers(self) -> list[Fetcher]: + def get_fetchers(self) -> list[Fetcher]: return list(self.fetcher_repo.values()) - async def add_metric(self, metric: MetricCreateSchema): + def add_metric(self, metric: MetricCreateSchema): self.metric_repo[self.id_incr] = metric self.id_incr += 1 - async def get_alerts(self) -> list[Alert]: + def get_alerts(self) -> list[Alert]: return list(self.alert_repo.values()) diff --git a/ping/service/exceptions.py b/pinger_backend/service/exceptions.py similarity index 65% rename from ping/service/exceptions.py rename to pinger_backend/service/exceptions.py index f22c2d1..b4a8760 100644 --- a/ping/service/exceptions.py +++ b/pinger_backend/service/exceptions.py @@ -3,6 +3,11 @@ def __init__(self): super().__init__("Scheduler is already running") +class ConnectionFail(Exception): + def __init__(self): + super().__init__("Failed to connect while fetching") + + class AlreadyStopped(Exception): def __init__(self): super().__init__("Scheduler is already stopped") \ No newline at end of file diff --git a/ping/service/scheduler.py b/pinger_backend/service/scheduler.py similarity index 79% rename from ping/service/scheduler.py rename to pinger_backend/service/scheduler.py index 64fd3f1..ef3628a 100644 --- a/ping/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -7,10 +7,10 @@ from aciniformes_backend.models import Alert, Fetcher, FetcherType from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from ping.settings import get_settings +from pinger_backend.settings import get_settings from .crud import CrudServiceInterface -from .exceptions import AlreadyRunning, AlreadyStopped +from .exceptions import AlreadyRunning, AlreadyStopped, ConnectionFail settings = get_settings() @@ -57,26 +57,26 @@ class FakeSchedulerService(SchedulerServiceInterface): def __init__(self, crud_service: CrudServiceInterface): self.crud_service = crud_service - async def add_fetcher(self, fetcher: Fetcher): + def add_fetcher(self, fetcher: Fetcher): self.scheduler[fetcher.id_] = fetcher - async def delete_fetcher(self, fetcher: Fetcher): + def delete_fetcher(self, fetcher: Fetcher): del self.scheduler[fetcher.id_] - async def get_jobs(self): - return await self.crud_service.get_fetchers() + def get_jobs(self): + return self.crud_service.get_fetchers() - async def start(self): + def start(self): if "started" in self.scheduler: raise AlreadyRunning self.scheduler["started"] = True - async def stop(self): + def stop(self): if not self.scheduler["started"]: raise AlreadyStopped self.scheduler["started"] = False - async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): + def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): httpx.post(f"{settings.BOT_URL}/alert", json=metric_log.json()) @@ -86,7 +86,7 @@ class ApSchedulerService(SchedulerServiceInterface): def __init__(self, crud_service: CrudServiceInterface): self.crud_service = crud_service - async def add_fetcher(self, fetcher: Fetcher): + def add_fetcher(self, fetcher: Fetcher): self.scheduler.add_job( self._fetch_it, args=[fetcher], @@ -95,40 +95,40 @@ async def add_fetcher(self, fetcher: Fetcher): trigger="interval", ) - async def delete_fetcher(self, fetcher: Fetcher): - self.scheduler.remove_job(fetcher.name) + def delete_fetcher(self, fetcher: Fetcher): + self.scheduler.remove_job(f"{fetcher.address} {fetcher.create_ts}") - async def get_jobs(self): + def get_jobs(self): return [j.id for j in self.scheduler.get_jobs()] - async def start(self): + def start(self): if self.scheduler.running: raise AlreadyRunning fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() self.scheduler.start() for fetcher in fetchers: fetcher = Fetcher(**fetcher) - await self.add_fetcher(fetcher) - await self._fetch_it(fetcher) + self.add_fetcher(fetcher) + self._fetch_it(fetcher) - async def stop(self): + def stop(self): if not self.scheduler.running: raise AlreadyStopped for job in self.scheduler.get_jobs(): job.remove() self.scheduler.shutdown() - async def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): + def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): receivers = httpx.get(f"{settings.BACKEND_URL}/receiver").json() for receiver in receivers: receiver['receiver_body']['text'] = metric_log httpx.post(receiver['url'], data=receiver['receiver_body']) @staticmethod - async def _parse_timedelta(fetcher: Fetcher): + def _parse_timedelta(fetcher: Fetcher): return fetcher.delay_ok, fetcher.delay_fail - async def _fetch_it(self, fetcher: Fetcher): + def _fetch_it(self, fetcher: Fetcher): prev = time.time() res = None try: @@ -147,23 +147,23 @@ async def _fetch_it(self, fetcher: Fetcher): ok=True if res and res.status_code == 200 else False, time_delta=timing ) - await self.crud_service.add_metric(metric) + self.crud_service.add_metric(metric) alert = AlertCreateSchema(data=metric, filter=500) self.scheduler.reschedule_job( f"{fetcher.address} {fetcher.create_ts}", seconds=fetcher.delay_fail, trigger="interval", ) - await self.write_alert(metric, alert) + self.write_alert(metric, alert) return cur = time.time() timing = cur - prev metric = MetricCreateSchema( name=fetcher.address, - ok=True if res and res.status_code == 200 else False, + ok=True if res and (200 <= res.status_code <= 300) else False, time_delta=timing ) - await self.crud_service.add_metric(metric) + self.crud_service.add_metric(metric) if not metric.ok: alert = AlertCreateSchema(data=metric, filter=res.status_code) self.scheduler.reschedule_job( @@ -171,7 +171,7 @@ async def _fetch_it(self, fetcher: Fetcher): seconds=fetcher.delay_fail, trigger="interval", ) - await self.write_alert(metric, alert) + self.write_alert(metric, alert) else: self.scheduler.reschedule_job( f"{fetcher.address} {fetcher.create_ts}", diff --git a/ping/settings.py b/pinger_backend/settings.py similarity index 100% rename from ping/settings.py rename to pinger_backend/settings.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bf2a06d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.black] +line-length = 120 +target-version = ['py311'] +skip-string-normalization = true + +[tool.isort] +line_length = 120 +multi_line_output = 3 +profile = "black" +lines_after_imports = 2 +include_trailing_comma = true + +[tool.pytest.ini_options] +minversion = "7.0" +python_files = "*.py" +testpaths = [ + "tests" +] +pythonpath = [ + "." +] +log_cli=true +log_level=0 diff --git a/requirements.txt b/requirements.txt index a5efe85..9e9a81c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,18 @@ -fastapi~=0.89.1 -sqlalchemy~=2.0.0 -pydantic~=1.10.4 -uvicorn~=0.20.0 -alembic~=1.9.2 +fastapi +sqlalchemy +pydantic +uvicorn +alembic python-dotenv fastapi-sqlalchemy psycopg2-binary gunicorn -starlette~=0.22.0 -pytest~=7.2.1 +starlette +pytest python-jose python-multipart -passlib~=1.7.4 -bcrypt -APScheduler~=3.10.0 -jose~=1.0.0 -auth-lib-profcomff~=2023.3.16.2 -asyncio~=3.4.3 - -httpx~=0.23.3 -aiogram~=2.25.1 \ No newline at end of file +APScheduler +jose +auth-lib-profcomff +asyncio +httpx \ No newline at end of file diff --git a/tests/backend/api/conftest.py b/tests/backend/api/conftest.py index 335481b..7f46c4e 100644 --- a/tests/backend/api/conftest.py +++ b/tests/backend/api/conftest.py @@ -1,42 +1,10 @@ import pytest from fastapi.testclient import TestClient -from pytest_mock import MockerFixture from aciniformes_backend.routes.base import app @pytest.fixture -def client(mocker: MockerFixture): - user_mock = mocker.patch("auth_lib.fastapi.UnionAuth.__call__") - user_mock.return_value = { - "session_scopes": [ - {"id": 53, "name": "pinger.alert.create"}, - {"id": 56, "name": "pinger.receiver.create"}, - {"id": 61, "name": "pinger.fetcher.update"}, - {"id": 62, "name": "pinger.metric.create"}, - {"id": 60, "name": "pinger.fetcher.delete"}, - {"id": 57, "name": "pinger.receiver.delete"}, - {"id": 54, "name": "pinger.alert.delete"}, - {"id": 58, "name": "pinger.receiver.update"}, - {"id": 59, "name": "pinger.fetcher.create"}, - {"id": 55, "name": "pinger.alert.update"}, - ], - "user_scopes": [ - {"id": 53, "name": "pinger.alert.create"}, - {"id": 56, "name": "pinger.receiver.create"}, - {"id": 61, "name": "pinger.fetcher.update"}, - {"id": 62, "name": "pinger.metric.create"}, - {"id": 60, "name": "pinger.fetcher.delete"}, - {"id": 57, "name": "pinger.receiver.delete"}, - {"id": 54, "name": "pinger.alert.delete"}, - {"id": 58, "name": "pinger.receiver.update"}, - {"id": 59, "name": "pinger.fetcher.create"}, - {"id": 55, "name": "pinger.alert.update"}, - ], - "indirect_groups": [{"id": 0, "name": "string", "parent_id": 0}], - "groups": [{"id": 0, "name": "string", "parent_id": 0}], - "id": 0, - "email": "string", - } +def client(): client = TestClient(app) return client diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 92ba25f..361b94e 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -16,7 +16,7 @@ def test_fake_service(fake_config): def this_fetcher(): body = { "id": 6, - "type_": "ping", + "type_": "pinger_backend", "address": "https://www.python.org", "fetch_data": "string", "delay_ok": 30, diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index 48fd9bf..d0f7bd0 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -6,7 +6,7 @@ from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker from aciniformes_backend.models.base import BaseModel -from ping.service import Config +from pinger_backend.service import Config @pytest.fixture(scope="session") diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index 5235bb2..05cdc62 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -1,6 +1,6 @@ import pytest -from ping.service import Config, crud_service, scheduler_service +from pinger_backend.service import Config, crud_service, scheduler_service @pytest.fixture diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index d0a7cb2..637dfef 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,8 +1,5 @@ import pytest -import httpx -import ping.service.exceptions as exc from aciniformes_backend.models import Fetcher, Metric -from ping.settings import get_settings @pytest.fixture() @@ -10,7 +7,7 @@ def fetcher_obj(): yield Fetcher( **{ "type_": "ping", - "address": "https://www.python.org", + "address": "http://testserver", "fetch_data": "string", "delay_ok": 30, "delay_fail": 40, @@ -23,52 +20,47 @@ class TestSchedulerService: async def test_add_fetcher_success( self, pg_scheduler_service, fake_crud_service, fetcher_obj ): - await pg_scheduler_service.add_fetcher(fetcher_obj) - fetchers = await pg_scheduler_service.get_jobs() + pg_scheduler_service.add_fetcher(fetcher_obj) + fetchers = pg_scheduler_service.get_jobs() assert f'{fetcher_obj.address} None' in fetchers @pytest.mark.asyncio async def test_delete_fetcher( self, pg_scheduler_service, fake_crud_service, fetcher_obj ): - pg_scheduler_service.delete_fetcher( - f"{fetcher_obj.address} {fetcher_obj.create_ts}" - ) - fetchers = await pg_scheduler_service.get_jobs() + pg_scheduler_service.add_fetcher(fetcher_obj) + fetchers = pg_scheduler_service.get_jobs() + assert f"{fetcher_obj.address} {fetcher_obj.create_ts}" in fetchers + + pg_scheduler_service.delete_fetcher(fetcher_obj) + fetchers = pg_scheduler_service.get_jobs() assert fetcher_obj not in fetchers @pytest.mark.asyncio async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): - res = await pg_scheduler_service.get_jobs() + res = pg_scheduler_service.get_jobs() assert type(res) is list @pytest.mark.asyncio async def test_start_already_started(self, pg_scheduler_service, fake_crud_service): + pg_scheduler_service.start() fail = False try: - await pg_scheduler_service.start() + pg_scheduler_service.start() except: fail = True assert fail @pytest.mark.asyncio - async def test_ping(self, pg_scheduler_service, fake_crud_service, dbsession): - fetcher = Fetcher(**{ - "type_": "get_ok", - "address": "https://www.python.org", - "fetch_data": "string", - "delay_ok": 30, - "delay_fail": 40, - } - ) - pg_scheduler_service.add_fetcher(fetcher) - pg_scheduler_service._fetch_it(fetcher) + async def test_ping(self, pg_scheduler_service, fetcher_obj, fake_crud_service, dbsession): + pg_scheduler_service.add_fetcher(fetcher_obj) + pg_scheduler_service._fetch_it(fetcher_obj) metrics = dbsession.query(Metric).all() for metric in metrics: - if metric.name == fetcher.address: + if metric.name == fetcher_obj.address: assert metric['ok'] fetcher = Fetcher(**{ - "type_": "get_ok", + "type_": "ping", "address": "https://www.ayyylmaorofl.org", "fetch_data": "string", "delay_ok": 30, @@ -80,4 +72,4 @@ async def test_ping(self, pg_scheduler_service, fake_crud_service, dbsession): metrics = dbsession.query(Metric).all() for metric in metrics: if metric['name'] == fetcher.address: - assert not metric['ok'] \ No newline at end of file + assert not metric['ok'] From 3c37886b1d0c8a8a74a933bf56cc948f06f62023 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 15 May 2023 15:10:10 +0300 Subject: [PATCH 26/53] Fix tests (kinda) --- .github/workflows/tests.yml | 43 ++++++++++++++++++--- aciniformes_backend/serivce/receiver.py | 2 +- tests/backend/conftest.py | 3 ++ tests/backend/service/test_alert_serivce.py | 4 +- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1822a7c..e3ee681 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: Tests +name: Python package on: pull_request: @@ -8,9 +8,10 @@ jobs: test: name: Unit tests runs-on: ubuntu-latest + continue-on-error: true steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@master - name: Set up docker uses: docker-practice/actions-setup-docker@master - name: Run postgres @@ -18,21 +19,51 @@ jobs: docker run -d -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --name db-test postgres:15-alpine - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Install dependencies run: | python -m ensurepip python -m pip install --upgrade pip pip install -r requirements.txt -r requirements.dev.txt + python -m acinifomes_backend - name: Migrate DB run: | DB_DSN=postgresql://postgres@localhost:5432/postgres alembic upgrade head - name: Build coverage file run: | - DB_DSN=postgresql://postgres@localhost:5432/postgres pytest --cache-clear --cov=aciniformes_backend tests > pytest-coverage.txt + DB_DSN=postgresql://postgres@localhost:5432/postgres pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=auth_backend tests/ | tee pytest-coverage.txt - name: Print report if: always() run: | cat pytest-coverage.txt - - name: Comment coverage - uses: coroo/pytest-coverage-commentator@v1.0.2 + - name: Pytest coverage comment + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-coverage-path: ./pytest-coverage.txt + title: Coverage Report + badge-title: Code Coverage + hide-badge: false + hide-report: false + create-new-comment: false + hide-comment: false + report-only-changed-files: false + remove-link-from-badge: false + junitxml-path: ./pytest.xml + junitxml-title: Summary + linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.11 + - uses: isort/isort-action@master + with: + requirementsFiles: "requirements.txt requirements.dev.txt" + - uses: psf/black@stable + - name: Comment if linting failed + if: ${{ failure() }} + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + :poop: Code linting failed, use `black` and `isort` to fix it. \ No newline at end of file diff --git a/aciniformes_backend/serivce/receiver.py b/aciniformes_backend/serivce/receiver.py index 494e9ab..51c711e 100644 --- a/aciniformes_backend/serivce/receiver.py +++ b/aciniformes_backend/serivce/receiver.py @@ -15,7 +15,7 @@ async def create(self, item: dict) -> int: self.session.flush() return receiver.id_ - async def get_by_id(self, id_: int) -> Type[db_models.Receiver]: + def get_by_id(self, id_: int) -> Type[db_models.Receiver]: q = sa.select(db_models.Receiver).where(db_models.Receiver.id_ == id_) res = self.session.scalar(q) if not res: diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index aa6be8c..a187f7f 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -7,6 +7,7 @@ from aciniformes_backend.routes.base import app from aciniformes_backend.serivce import Config from aciniformes_backend.settings import get_settings +from pinger_backend.settings import get_settings as settings_backend @pytest.fixture(scope="session") @@ -44,4 +45,6 @@ def fake_config(): @pytest.fixture def client(fake_config): client = TestClient(app) + settings = settings_backend() + settings.BACKEND_URL = "http://testserver" return client diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index 53826d3..85bab8f 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -80,11 +80,11 @@ async def test_get_all(self, pg_receiver_service, db_receiver, db_alert): @pytest.mark.asyncio async def test_get_by_id(self, pg_receiver_service, db_receiver): - res = await pg_receiver_service.get_by_id(db_receiver.id_) + res = pg_receiver_service.get_by_id(db_receiver.id_) assert res is not None assert res.url == db_receiver.url with pytest.raises(exc.ObjectNotFound): - await pg_receiver_service.get_by_id(db_receiver.id_ + 1000) + pg_receiver_service.get_by_id(db_receiver.id_ + 1000) @pytest.mark.asyncio async def test_delete(self, pg_receiver_service, db_receiver): From bada52627d652c0902eecae5a7ece98b921bfd4d Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 15 May 2023 15:13:29 +0300 Subject: [PATCH 27/53] Fixing again --- .github/workflows/tests.yml | 2 +- aciniformes_backend/__main__.py | 1 + aciniformes_backend/models/__init__.py | 1 + aciniformes_backend/models/alerts.py | 6 +- aciniformes_backend/models/fetcher.py | 8 +-- aciniformes_backend/models/metric.py | 2 +- aciniformes_backend/routes/alert/alert.py | 3 +- aciniformes_backend/routes/alert/reciever.py | 10 ++- aciniformes_backend/routes/base.py | 3 +- aciniformes_backend/routes/fetcher.py | 3 +- aciniformes_backend/routes/mectric.py | 3 +- aciniformes_backend/serivce/__init__.py | 9 +-- aciniformes_backend/serivce/alert.py | 7 +-- aciniformes_backend/serivce/bootstrap.py | 7 +-- aciniformes_backend/serivce/fake.py | 7 +-- aciniformes_backend/serivce/fetcher.py | 7 +-- aciniformes_backend/settings.py | 1 + migrations/env.py | 1 + migrations/versions/febba504289a_init.py | 62 ++++++++++--------- pinger_backend/__main__.py | 3 +- pinger_backend/service/__init__.py | 1 + pinger_backend/service/exceptions.py | 2 +- pinger_backend/service/scheduler.py | 9 +-- tests/backend/api/test_alert.py | 19 +----- tests/backend/api/test_fetcher.py | 12 ++-- tests/backend/api/test_metric.py | 13 +--- tests/backend/conftest.py | 4 +- tests/backend/service/conftest.py | 8 +-- tests/backend/service/test_alert_serivce.py | 34 ++-------- tests/backend/service/test_fetcher_service.py | 20 +++--- tests/backend/service/test_metric_service.py | 12 +--- tests/ping_service/conftest.py | 6 +- tests/ping_service/service/test_scheduler.py | 22 +++---- 33 files changed, 112 insertions(+), 196 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3ee681..6f86fb1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,12 +25,12 @@ jobs: python -m ensurepip python -m pip install --upgrade pip pip install -r requirements.txt -r requirements.dev.txt - python -m acinifomes_backend - name: Migrate DB run: | DB_DSN=postgresql://postgres@localhost:5432/postgres alembic upgrade head - name: Build coverage file run: | + python -m acinifomes_backend DB_DSN=postgresql://postgres@localhost:5432/postgres pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=auth_backend tests/ | tee pytest-coverage.txt - name: Print report if: always() diff --git a/aciniformes_backend/__main__.py b/aciniformes_backend/__main__.py index d75eb22..a0a3f7d 100644 --- a/aciniformes_backend/__main__.py +++ b/aciniformes_backend/__main__.py @@ -2,5 +2,6 @@ from .routes import app + if __name__ == "__main__": uvicorn.run(app) diff --git a/aciniformes_backend/models/__init__.py b/aciniformes_backend/models/__init__.py index 10e447d..160d034 100644 --- a/aciniformes_backend/models/__init__.py +++ b/aciniformes_backend/models/__init__.py @@ -3,4 +3,5 @@ from .fetcher import Fetcher, FetcherType from .metric import Metric + __all__ = ["Metric", "Fetcher", "Alert", "Receiver", "BaseModel", "FetcherType"] diff --git a/aciniformes_backend/models/alerts.py b/aciniformes_backend/models/alerts.py index 628b2d2..fbbbdb9 100644 --- a/aciniformes_backend/models/alerts.py +++ b/aciniformes_backend/models/alerts.py @@ -1,12 +1,14 @@ """Классы хранения настроек нотификаций """ from datetime import datetime +from enum import Enum -from sqlalchemy import JSON, DateTime, ForeignKey, Integer, String, Enum as DbEnum +from sqlalchemy import JSON, DateTime +from sqlalchemy import Enum as DbEnum +from sqlalchemy import ForeignKey, Integer, String from sqlalchemy.orm import Mapped, mapped_column from .base import BaseModel -from enum import Enum class Method(str, Enum): diff --git a/aciniformes_backend/models/fetcher.py b/aciniformes_backend/models/fetcher.py index 996bd8c..fb5df5b 100644 --- a/aciniformes_backend/models/fetcher.py +++ b/aciniformes_backend/models/fetcher.py @@ -18,13 +18,9 @@ class FetcherType(str, Enum): class Fetcher(BaseModel): id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) - type_: Mapped[FetcherType] = mapped_column( - "type", sqlalchemy.Enum(FetcherType, native_enum=False), nullable=False - ) + type_: Mapped[FetcherType] = mapped_column("type", sqlalchemy.Enum(FetcherType, native_enum=False), nullable=False) address: Mapped[str] = mapped_column(String, nullable=False) - fetch_data: Mapped[str] = mapped_column( - String, nullable=True - ) # Данные, которые передаются в теле POST запроса + fetch_data: Mapped[str] = mapped_column(String, nullable=True) # Данные, которые передаются в теле POST запроса delay_ok: Mapped[int] = mapped_column( Integer, default=300, nullable=False ) # Через сколько секунд повторить запрос, если предыдущий успешный diff --git a/aciniformes_backend/models/metric.py b/aciniformes_backend/models/metric.py index c21d4be..050c5c1 100644 --- a/aciniformes_backend/models/metric.py +++ b/aciniformes_backend/models/metric.py @@ -3,7 +3,7 @@ from datetime import datetime -from sqlalchemy import DateTime, Integer, String, Boolean, Float +from sqlalchemy import Boolean, DateTime, Float, Integer, String from sqlalchemy.orm import Mapped, mapped_column from .base import BaseModel diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 4a1ac64..68bc0ee 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -1,8 +1,9 @@ +import logging + from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel from starlette import status -import logging from aciniformes_backend.serivce import AlertServiceInterface, alert_service from aciniformes_backend.serivce import exceptions as exc diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index 878d97f..19218df 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -1,9 +1,10 @@ +import enum +import logging + from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel from starlette import status -import enum -import logging from aciniformes_backend.serivce import ReceiverServiceInterface from aciniformes_backend.serivce import exceptions as exc @@ -46,10 +47,7 @@ class GetSchema(BaseModel): @router.post("", response_model=PostResponseSchema) -async def create( - create_schema: CreateSchema, - receiver: ReceiverServiceInterface = Depends(receiver_service) -): +async def create(create_schema: CreateSchema, receiver: ReceiverServiceInterface = Depends(receiver_service)): logger.info(f"Someone triggered create_receiver") id_ = await receiver.create(create_schema.dict()) return PostResponseSchema(**create_schema.dict(), id=id_) diff --git a/aciniformes_backend/routes/base.py b/aciniformes_backend/routes/base.py index c0e782c..ca84348 100644 --- a/aciniformes_backend/routes/base.py +++ b/aciniformes_backend/routes/base.py @@ -8,6 +8,7 @@ from .fetcher import router as fetcher_router from .mectric import router as metric_router + app = FastAPI() app.include_router(alert_router, prefix="/alert", tags=["Alert"]) app.include_router(receiver_router, prefix="/receiver", tags=["Receiver"]) @@ -17,5 +18,5 @@ app.add_middleware( DBSessionMiddleware, db_url=get_settings().DB_DSN, - engine_args={"pool_pre_ping": True, "isolation_level": "AUTOCOMMIT"} + engine_args={"pool_pre_ping": True, "isolation_level": "AUTOCOMMIT"}, ) diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index c8cf0ec..b3b2db1 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -4,12 +4,13 @@ from fastapi.exceptions import HTTPException from pydantic import BaseModel, HttpUrl from starlette import status -from aciniformes_backend.models.fetcher import FetcherType +from aciniformes_backend.models.fetcher import FetcherType from aciniformes_backend.serivce import FetcherServiceInterface from aciniformes_backend.serivce import exceptions as exc from aciniformes_backend.serivce import fetcher_service + logger = logging.getLogger(__name__) router = APIRouter() diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index 3429090..8faba0b 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -1,8 +1,9 @@ +from datetime import datetime + from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel from starlette import status -from datetime import datetime from aciniformes_backend.serivce import MetricServiceInterface from aciniformes_backend.serivce import exceptions as exc diff --git a/aciniformes_backend/serivce/__init__.py b/aciniformes_backend/serivce/__init__.py index 1494168..087d647 100644 --- a/aciniformes_backend/serivce/__init__.py +++ b/aciniformes_backend/serivce/__init__.py @@ -5,13 +5,8 @@ MetricServiceInterface, ReceiverServiceInterface, ) -from .bootstrap import ( - Config, - alert_service, - fetcher_service, - metric_service, - receiver_service, -) +from .bootstrap import Config, alert_service, fetcher_service, metric_service, receiver_service + __all__ = [ "AlertServiceInterface", diff --git a/aciniformes_backend/serivce/alert.py b/aciniformes_backend/serivce/alert.py index 3677254..892c4c7 100644 --- a/aciniformes_backend/serivce/alert.py +++ b/aciniformes_backend/serivce/alert.py @@ -26,12 +26,7 @@ async def delete(self, id_: int) -> None: self.session.flush() async def update(self, id_: int, item: dict) -> db_models.Alert: - q = ( - sa.update(db_models.Alert) - .where(db_models.Alert.id_ == id_) - .values(**item) - .returning(db_models.Alert) - ) + q = sa.update(db_models.Alert).where(db_models.Alert.id_ == id_).values(**item).returning(db_models.Alert) if not self.get_by_id(id_): raise exc.ObjectNotFound(id_) res = self.session.execute(q).scalar() diff --git a/aciniformes_backend/serivce/bootstrap.py b/aciniformes_backend/serivce/bootstrap.py index a5de380..9eae013 100644 --- a/aciniformes_backend/serivce/bootstrap.py +++ b/aciniformes_backend/serivce/bootstrap.py @@ -1,12 +1,7 @@ from fastapi_sqlalchemy import db from .alert import PgAlertService -from .fake import ( - FakeAlertService, - FakeFetcherService, - FakeMetricService, - FakeReceiverService, -) +from .fake import FakeAlertService, FakeFetcherService, FakeMetricService, FakeReceiverService from .fetcher import PgFetcherService from .metric import PgMetricService from .receiver import PgReceiverService diff --git a/aciniformes_backend/serivce/fake.py b/aciniformes_backend/serivce/fake.py index d013612..7607e62 100644 --- a/aciniformes_backend/serivce/fake.py +++ b/aciniformes_backend/serivce/fake.py @@ -4,12 +4,7 @@ import aciniformes_backend.serivce.exceptions as exc from aciniformes_backend.settings import get_settings -from .base import ( - AlertServiceInterface, - FetcherServiceInterface, - MetricServiceInterface, - ReceiverServiceInterface, -) +from .base import AlertServiceInterface, FetcherServiceInterface, MetricServiceInterface, ReceiverServiceInterface class FakeAlertService(AlertServiceInterface): diff --git a/aciniformes_backend/serivce/fetcher.py b/aciniformes_backend/serivce/fetcher.py index e1be5f3..fbb1cf6 100644 --- a/aciniformes_backend/serivce/fetcher.py +++ b/aciniformes_backend/serivce/fetcher.py @@ -26,12 +26,7 @@ async def delete(self, id_: int) -> None: self.session.flush() async def update(self, id_: int, item: dict) -> db_models.Fetcher: - q = ( - sa.update(db_models.Fetcher) - .where(db_models.Fetcher.id_ == id_) - .values(**item) - .returning(db_models.Fetcher) - ) + q = sa.update(db_models.Fetcher).where(db_models.Fetcher.id_ == id_).values(**item).returning(db_models.Fetcher) if not await self.get_by_id(id_): raise exc.ObjectNotFound(id_) res = self.session.execute(q).scalar() diff --git a/aciniformes_backend/settings.py b/aciniformes_backend/settings.py index 92856f2..557b2ba 100644 --- a/aciniformes_backend/settings.py +++ b/aciniformes_backend/settings.py @@ -1,4 +1,5 @@ from functools import lru_cache + from pydantic import BaseSettings, PostgresDsn diff --git a/migrations/env.py b/migrations/env.py index 841a538..867c241 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -6,6 +6,7 @@ from aciniformes_backend.models import BaseModel from aciniformes_backend.settings import get_settings + config = context.config settings = get_settings() diff --git a/migrations/versions/febba504289a_init.py b/migrations/versions/febba504289a_init.py index 0bf098b..36a7166 100644 --- a/migrations/versions/febba504289a_init.py +++ b/migrations/versions/febba504289a_init.py @@ -5,8 +5,8 @@ Create Date: 2023-04-24 13:29:41.968973 """ -from alembic import op import sqlalchemy as sa +from alembic import op revision = 'febba504289a' @@ -16,37 +16,41 @@ def upgrade() -> None: - op.create_table('alert', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('data', sa.JSON(), nullable=False), - sa.Column('filter', sa.String(), nullable=False), - sa.Column('create_ts', sa.DateTime(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'alert', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('data', sa.JSON(), nullable=False), + sa.Column('filter', sa.String(), nullable=False), + sa.Column('create_ts', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id'), ) - op.create_table('fetcher', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('type', sa.Enum('GET', 'POST', 'PING', name='fetchertype', native_enum=False), nullable=False), - sa.Column('address', sa.String(), nullable=False), - sa.Column('fetch_data', sa.String(), nullable=True), - sa.Column('delay_ok', sa.Integer(), nullable=False), - sa.Column('delay_fail', sa.Integer(), nullable=False), - sa.Column('create_ts', sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'fetcher', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('type', sa.Enum('GET', 'POST', 'PING', name='fetchertype', native_enum=False), nullable=False), + sa.Column('address', sa.String(), nullable=False), + sa.Column('fetch_data', sa.String(), nullable=True), + sa.Column('delay_ok', sa.Integer(), nullable=False), + sa.Column('delay_fail', sa.Integer(), nullable=False), + sa.Column('create_ts', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id'), ) - op.create_table('metric', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(), nullable=False), - sa.Column('ok', sa.Boolean(), nullable=False), - sa.Column('time_delta', sa.Float(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'metric', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('ok', sa.Boolean(), nullable=False), + sa.Column('time_delta', sa.Float(), nullable=False), + sa.PrimaryKeyConstraint('id'), ) - op.create_table('receiver', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('url', sa.String(), nullable=False), - sa.Column('method', sa.Enum('POST', 'GET', name='method', native_enum=False), nullable=False), - sa.Column('receiver_body', sa.JSON(), nullable=False), - sa.Column('create_ts', sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + 'receiver', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('url', sa.String(), nullable=False), + sa.Column('method', sa.Enum('POST', 'GET', name='method', native_enum=False), nullable=False), + sa.Column('receiver_body', sa.JSON(), nullable=False), + sa.Column('create_ts', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id'), ) diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index ffe1e65..7fb3025 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -1,7 +1,8 @@ import asyncio -from pinger_backend.service.scheduler import ApSchedulerService from pinger_backend.service.crud import CrudService +from pinger_backend.service.scheduler import ApSchedulerService + if __name__ == "__main__": loop = asyncio.new_event_loop() diff --git a/pinger_backend/service/__init__.py b/pinger_backend/service/__init__.py index 95c2f69..076add7 100644 --- a/pinger_backend/service/__init__.py +++ b/pinger_backend/service/__init__.py @@ -2,6 +2,7 @@ from .crud import CrudServiceInterface from .scheduler import SchedulerServiceInterface + __all__ = [ "CrudServiceInterface", "SchedulerServiceInterface", diff --git a/pinger_backend/service/exceptions.py b/pinger_backend/service/exceptions.py index b4a8760..1c985da 100644 --- a/pinger_backend/service/exceptions.py +++ b/pinger_backend/service/exceptions.py @@ -10,4 +10,4 @@ def __init__(self): class AlreadyStopped(Exception): def __init__(self): - super().__init__("Scheduler is already stopped") \ No newline at end of file + super().__init__("Scheduler is already stopped") diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index ef3628a..cc632d5 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -12,6 +12,7 @@ from .crud import CrudServiceInterface from .exceptions import AlreadyRunning, AlreadyStopped, ConnectionFail + settings = get_settings() @@ -143,9 +144,7 @@ def _fetch_it(self, fetcher: Fetcher): cur = time.time() timing = cur - prev metric = MetricCreateSchema( - name=fetcher.address, - ok=True if res and res.status_code == 200 else False, - time_delta=timing + name=fetcher.address, ok=True if res and res.status_code == 200 else False, time_delta=timing ) self.crud_service.add_metric(metric) alert = AlertCreateSchema(data=metric, filter=500) @@ -159,9 +158,7 @@ def _fetch_it(self, fetcher: Fetcher): cur = time.time() timing = cur - prev metric = MetricCreateSchema( - name=fetcher.address, - ok=True if res and (200 <= res.status_code <= 300) else False, - time_delta=timing + name=fetcher.address, ok=True if res and (200 <= res.status_code <= 300) else False, time_delta=timing ) self.crud_service.add_metric(metric) if not metric.ok: diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 0927ace..7945fb7 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -87,12 +87,7 @@ def test_patch_by_id_not_found(self, client, this_alert): @pytest.fixture def this_receiver(): - body = { - "id": 4, - "url": "string", - "method": "post", - "receiver_body": {} - } + body = {"id": 4, "url": "string", "method": "post", "receiver_body": {}} receiver_service().repository[body["id"]] = body return body @@ -103,11 +98,7 @@ class TestReceiver: s = receiver_service() def test_post_success(self, client): - body = { - "url": "string", - "method": "post", - "receiver_body": {} - } + body = {"url": "string", "method": "post", "receiver_body": {}} res = client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK res_body = res.json() @@ -132,11 +123,7 @@ def test_get_success(self, client, this_receiver): assert len(res.json()) def test_patch_by_id_success(self, client, this_receiver): - body = { - "url": "sdasd", - "method": "post", - "receiver_body": {} - } + body = {"url": "sdasd", "method": "post", "receiver_body": {}} res = client.patch( f"{self._url}/{this_receiver['id']}", data=json.dumps(body), diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 361b94e..125c261 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -61,12 +61,12 @@ def test_get_success(self, client, this_fetcher): def test_patch_by_id_success(self, client, this_fetcher): body = { - "type_": "post", - "address": "https://api.test.profcomff.com/services/category", - "fetch_data": "string", - "delay_ok": 300, - "delay_fail": 30 - } + "type_": "post", + "address": "https://api.test.profcomff.com/services/category", + "fetch_data": "string", + "delay_ok": 300, + "delay_fail": 30, + } res = client.patch( f"{self._url}/{this_fetcher['id']}", json=body, diff --git a/tests/backend/api/test_metric.py b/tests/backend/api/test_metric.py index c91c936..1f6415e 100644 --- a/tests/backend/api/test_metric.py +++ b/tests/backend/api/test_metric.py @@ -8,12 +8,7 @@ @pytest.fixture def this_metric(): - body = { - "id": 5, - "name": "string", - "ok": True, - "time_delta": 0 - } + body = {"id": 5, "name": "string", "ok": True, "time_delta": 0} metric_service().repository[body["id"]] = body return body @@ -24,11 +19,7 @@ class TestMetric: s = metric_service() def test_post_success(self, client): - body = { - "name": "string", - "ok": True, - "time_delta": 0 - } + body = {"name": "string", "ok": True, "time_delta": 0} print(self._url) res = client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index a187f7f..c4be8fd 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -12,9 +12,7 @@ @pytest.fixture(scope="session") def engine(): - return create_engine( - get_settings().DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"} - ) + return create_engine(get_settings().DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) @pytest.fixture(scope="session") diff --git a/tests/backend/service/conftest.py b/tests/backend/service/conftest.py index dc0696e..d829be6 100644 --- a/tests/backend/service/conftest.py +++ b/tests/backend/service/conftest.py @@ -1,12 +1,6 @@ import pytest -from aciniformes_backend.serivce import ( - Config, - alert_service, - fetcher_service, - metric_service, - receiver_service, -) +from aciniformes_backend.serivce import Config, alert_service, fetcher_service, metric_service, receiver_service @pytest.fixture diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index 85bab8f..3826fdd 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -6,29 +6,19 @@ import aciniformes_backend.serivce.exceptions as exc from aciniformes_backend.models import Alert, Receiver from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema -from aciniformes_backend.routes.alert.reciever import ( - CreateSchema as ReceiverCreateSchema, -) +from aciniformes_backend.routes.alert.reciever import CreateSchema as ReceiverCreateSchema @pytest.fixture def receiver_schema(): - body = { - "url": "string", - "method": "post", - "receiver_body": {} - } + body = {"url": "string", "method": "post", "receiver_body": {}} schema = ReceiverCreateSchema(**body) return schema @pytest.fixture def db_receiver(dbsession, receiver_schema): - q = ( - sqlalchemy.insert(Receiver) - .values(**receiver_schema.dict(exclude_unset=True)) - .returning(Receiver) - ) + q = sqlalchemy.insert(Receiver).values(**receiver_schema.dict(exclude_unset=True)).returning(Receiver) receiver = dbsession.execute(q).scalar() dbsession.flush() yield receiver @@ -49,11 +39,7 @@ def alert_schema(receiver_schema): @pytest.fixture def db_alert(db_receiver, dbsession, alert_schema): - q = ( - sqlalchemy.insert(Alert) - .values(**alert_schema.dict(exclude_unset=True)) - .returning(Alert) - ) + q = sqlalchemy.insert(Alert).values(**alert_schema.dict(exclude_unset=True)).returning(Alert) alert = dbsession.execute(q).scalar() dbsession.flush() yield alert @@ -92,13 +78,7 @@ async def test_delete(self, pg_receiver_service, db_receiver): @pytest.mark.asyncio async def test_update(self, pg_receiver_service, db_receiver, dbsession): - res = await pg_receiver_service.update( - db_receiver.id_, { - "url": "Alex", - "method": "post", - "receiver_body": {} - } - ) + res = await pg_receiver_service.update(db_receiver.id_, {"url": "Alex", "method": "post", "receiver_body": {}}) assert res.url == "Alex" assert res.receiver_body == {} @@ -133,7 +113,5 @@ async def test_delete(self, pg_alert_service, db_alert): @pytest.mark.asyncio async def test_update(self, pg_alert_service, db_alert): - res = await pg_alert_service.update( - db_alert.id_, {"data": {"type": "stig", "name": "stig"}} - ) + res = await pg_alert_service.update(db_alert.id_, {"data": {"type": "stig", "name": "stig"}}) assert res.data == {"type": "stig", "name": "stig"} diff --git a/tests/backend/service/test_fetcher_service.py b/tests/backend/service/test_fetcher_service.py index 9bda4c1..e2469c2 100644 --- a/tests/backend/service/test_fetcher_service.py +++ b/tests/backend/service/test_fetcher_service.py @@ -8,24 +8,20 @@ @pytest.fixture def fetcher_schema(): body = { - "id": 6, - "type_": "ping", - "address": "https://www.python.org", - "fetch_data": "string", - "delay_ok": 30, - "delay_fail": 40, - } + "id": 6, + "type_": "ping", + "address": "https://www.python.org", + "fetch_data": "string", + "delay_ok": 30, + "delay_fail": 40, + } schema = FetcherCreateSchema(**body) return schema @pytest.fixture() def db_fetcher(dbsession, fetcher_schema): - q = ( - sqlalchemy.insert(Fetcher) - .values(**fetcher_schema.dict(exclude_unset=True)) - .returning(Fetcher) - ) + q = sqlalchemy.insert(Fetcher).values(**fetcher_schema.dict(exclude_unset=True)).returning(Fetcher) fetcher = dbsession.scalar(q) dbsession.flush() yield fetcher diff --git a/tests/backend/service/test_metric_service.py b/tests/backend/service/test_metric_service.py index edd7388..af66b1c 100644 --- a/tests/backend/service/test_metric_service.py +++ b/tests/backend/service/test_metric_service.py @@ -8,22 +8,14 @@ @pytest.fixture def metric_schema(): - body = {"id": 44, - "name": "string", - "ok": True, - "time_delta": 0 - } + body = {"id": 44, "name": "string", "ok": True, "time_delta": 0} schema = MetricCreateSchema(**body) return schema @pytest.fixture() def db_metric(dbsession, metric_schema): - q = ( - sqlalchemy.insert(Metric) - .values(**metric_schema.dict(exclude_unset=True)) - .returning(Metric) - ) + q = sqlalchemy.insert(Metric).values(**metric_schema.dict(exclude_unset=True)).returning(Metric) metric = dbsession.scalar(q) dbsession.flush() yield metric diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index d0f7bd0..6a3a5d2 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -1,11 +1,11 @@ import pytest from fastapi.testclient import TestClient - -from aciniformes_backend.routes import app -from aciniformes_backend.settings import get_settings from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker + from aciniformes_backend.models.base import BaseModel +from aciniformes_backend.routes import app +from aciniformes_backend.settings import get_settings from pinger_backend.service import Config diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 637dfef..814530e 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,4 +1,5 @@ import pytest + from aciniformes_backend.models import Fetcher, Metric @@ -17,17 +18,13 @@ def fetcher_obj(): class TestSchedulerService: @pytest.mark.asyncio - async def test_add_fetcher_success( - self, pg_scheduler_service, fake_crud_service, fetcher_obj - ): + async def test_add_fetcher_success(self, pg_scheduler_service, fake_crud_service, fetcher_obj): pg_scheduler_service.add_fetcher(fetcher_obj) fetchers = pg_scheduler_service.get_jobs() assert f'{fetcher_obj.address} None' in fetchers @pytest.mark.asyncio - async def test_delete_fetcher( - self, pg_scheduler_service, fake_crud_service, fetcher_obj - ): + async def test_delete_fetcher(self, pg_scheduler_service, fake_crud_service, fetcher_obj): pg_scheduler_service.add_fetcher(fetcher_obj) fetchers = pg_scheduler_service.get_jobs() assert f"{fetcher_obj.address} {fetcher_obj.create_ts}" in fetchers @@ -59,12 +56,13 @@ async def test_ping(self, pg_scheduler_service, fetcher_obj, fake_crud_service, for metric in metrics: if metric.name == fetcher_obj.address: assert metric['ok'] - fetcher = Fetcher(**{ - "type_": "ping", - "address": "https://www.ayyylmaorofl.org", - "fetch_data": "string", - "delay_ok": 30, - "delay_fail": 40, + fetcher = Fetcher( + **{ + "type_": "ping", + "address": "https://www.ayyylmaorofl.org", + "fetch_data": "string", + "delay_ok": 30, + "delay_fail": 40, } ) pg_scheduler_service.add_fetcher(fetcher) From f3949fff9f5164a4e0c239ae4fbfa85eca68730a Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Mon, 22 May 2023 20:10:22 +0300 Subject: [PATCH 28/53] Kinda fix but actually no --- pinger_backend/__main__.py | 6 +++++- pinger_backend/service/crud.py | 15 +++++++++------ pinger_backend/service/scheduler.py | 20 +++++++++----------- pinger_backend/service/session.py | 14 ++++++++++++++ tests/ping_service/service/conftest.py | 2 ++ tests/ping_service/service/test_scheduler.py | 14 ++++---------- 6 files changed, 43 insertions(+), 28 deletions(-) create mode 100644 pinger_backend/service/session.py diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 7fb3025..5ef2736 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -1,5 +1,6 @@ import asyncio +from .settings import get_settings from pinger_backend.service.crud import CrudService from pinger_backend.service.scheduler import ApSchedulerService @@ -7,5 +8,8 @@ if __name__ == "__main__": loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - loop.create_task(ApSchedulerService(CrudService()).start()) + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + scheduler = ApSchedulerService(CrudService()) + scheduler.backend_url = get_settings().BACKEND_URL + loop.create_task(scheduler.start()) loop.run_forever() diff --git a/pinger_backend/service/crud.py b/pinger_backend/service/crud.py index 62d7de3..5cce487 100644 --- a/pinger_backend/service/crud.py +++ b/pinger_backend/service/crud.py @@ -1,10 +1,9 @@ from abc import ABC, abstractmethod -import httpx - -from aciniformes_backend.models import Alert, Fetcher +from aciniformes_backend.models import Alert, Fetcher, Metric from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from pinger_backend.settings import get_settings +from pinger_backend.service.session import dbsession class CrudServiceInterface(ABC): @@ -28,13 +27,17 @@ def __init__(self): self.backend_url = get_settings().BACKEND_URL def get_fetchers(self) -> list[Fetcher]: - return [Fetcher(**d) for d in httpx.get(f"{self.backend_url}/fetcher").json()] + return [Fetcher(**d) for d in dbsession().query(Fetcher).all()] def add_metric(self, metric: MetricCreateSchema): - return httpx.post(f"{self.backend_url}/metric", data=metric.json()) + metric = Metric(**metric.dict(exclude_none=True)) + dbsession().add(metric) + dbsession().commit() + dbsession().flush() + return metric def get_alerts(self) -> list[Alert]: - return httpx.get(f"{self.backend_url}/alert").json() + return [Alert(**d) for d in dbsession().query(Alert).all()] class FakeCrudService(CrudServiceInterface): diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index cc632d5..9bc48da 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -4,16 +4,14 @@ import httpx from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler -from aciniformes_backend.models import Alert, Fetcher, FetcherType +from aciniformes_backend.models import Alert, Fetcher, FetcherType, Receiver from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from pinger_backend.settings import get_settings +from pinger_backend.settings import get_settings as backend_settings from .crud import CrudServiceInterface from .exceptions import AlreadyRunning, AlreadyStopped, ConnectionFail - - -settings = get_settings() +from pinger_backend.service.session import dbsession class SchedulerServiceInterface(ABC): @@ -78,11 +76,12 @@ def stop(self): self.scheduler["started"] = False def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - httpx.post(f"{settings.BOT_URL}/alert", json=metric_log.json()) + httpx.post(f"{backend_settings.BOT_URL}/alert", json=metric_log.json()) class ApSchedulerService(SchedulerServiceInterface): scheduler = AsyncIOScheduler() + backend_url = str def __init__(self, crud_service: CrudServiceInterface): self.crud_service = crud_service @@ -105,10 +104,9 @@ def get_jobs(self): def start(self): if self.scheduler.running: raise AlreadyRunning - fetchers = httpx.get(f"{settings.BACKEND_URL}/fetcher").json() + fetchers = dbsession().query(Fetcher).all() self.scheduler.start() for fetcher in fetchers: - fetcher = Fetcher(**fetcher) self.add_fetcher(fetcher) self._fetch_it(fetcher) @@ -120,10 +118,10 @@ def stop(self): self.scheduler.shutdown() def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): - receivers = httpx.get(f"{settings.BACKEND_URL}/receiver").json() + receivers = dbsession().query(Receiver).all() for receiver in receivers: - receiver['receiver_body']['text'] = metric_log - httpx.post(receiver['url'], data=receiver['receiver_body']) + receiver.receiver_body['text'] = metric_log + httpx.post(receiver.url, data=receiver['receiver_body']) @staticmethod def _parse_timedelta(fetcher: Fetcher): diff --git a/pinger_backend/service/session.py b/pinger_backend/service/session.py new file mode 100644 index 0000000..620a350 --- /dev/null +++ b/pinger_backend/service/session.py @@ -0,0 +1,14 @@ +from aciniformes_backend.settings import get_settings as db_settings +from aciniformes_backend.models.base import BaseModel +from sqlalchemy import create_engine +from sqlalchemy.orm import Session, sessionmaker + + +def dbsession() -> Session: + settings = db_settings() + engine = create_engine(settings.DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) + session = sessionmaker(bind=engine) + localsession = session() + BaseModel.metadata.create_all(bind=engine) + + return localsession diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index 05cdc62..92f061b 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -12,6 +12,7 @@ def pg_config(): @pytest.fixture def pg_scheduler_service(pg_config): s = scheduler_service() + s.backend_url = "http://testserver" assert s.scheduler is not dict yield s @@ -20,5 +21,6 @@ def pg_scheduler_service(pg_config): def fake_crud_service(pg_config): Config.fake = False s = crud_service() + s.backend_url = "http://testserver" yield s Config.fake = False diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 814530e..22ab06e 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -8,7 +8,7 @@ def fetcher_obj(): yield Fetcher( **{ "type_": "ping", - "address": "http://testserver", + "address": "localhost", "fetch_data": "string", "delay_ok": 30, "delay_fail": 40, @@ -39,7 +39,7 @@ async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): assert type(res) is list @pytest.mark.asyncio - async def test_start_already_started(self, pg_scheduler_service, fake_crud_service): + async def test_start_already_started(self, pg_scheduler_service, fake_crud_service, crud_client): pg_scheduler_service.start() fail = False try: @@ -49,17 +49,11 @@ async def test_start_already_started(self, pg_scheduler_service, fake_crud_servi assert fail @pytest.mark.asyncio - async def test_ping(self, pg_scheduler_service, fetcher_obj, fake_crud_service, dbsession): - pg_scheduler_service.add_fetcher(fetcher_obj) - pg_scheduler_service._fetch_it(fetcher_obj) - metrics = dbsession.query(Metric).all() - for metric in metrics: - if metric.name == fetcher_obj.address: - assert metric['ok'] + async def test_ping_fail(self, pg_scheduler_service, fetcher_obj, fake_crud_service, dbsession): fetcher = Fetcher( **{ "type_": "ping", - "address": "https://www.ayyylmaorofl.org", + "address": "fasdlj", "fetch_data": "string", "delay_ok": 30, "delay_fail": 40, From 11bd0620cba369b6cb585cfef4c668d6edcda990 Mon Sep 17 00:00:00 2001 From: Wudext Date: Sat, 27 May 2023 14:43:47 +0300 Subject: [PATCH 29/53] =?UTF-8?q?Fix=20test=20(finally=20=D0=B1=D0=BB?= =?UTF-8?q?=D1=8F=D1=82=D1=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pinger_backend/__main__.py | 3 ++- pinger_backend/service/crud.py | 2 +- pinger_backend/service/scheduler.py | 2 +- pinger_backend/service/session.py | 5 +++-- tests/ping_service/service/test_scheduler.py | 4 +++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 5ef2736..9386b1e 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -1,9 +1,10 @@ import asyncio -from .settings import get_settings from pinger_backend.service.crud import CrudService from pinger_backend.service.scheduler import ApSchedulerService +from .settings import get_settings + if __name__ == "__main__": loop = asyncio.new_event_loop() diff --git a/pinger_backend/service/crud.py b/pinger_backend/service/crud.py index 5cce487..1472ae1 100644 --- a/pinger_backend/service/crud.py +++ b/pinger_backend/service/crud.py @@ -2,8 +2,8 @@ from aciniformes_backend.models import Alert, Fetcher, Metric from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from pinger_backend.settings import get_settings from pinger_backend.service.session import dbsession +from pinger_backend.settings import get_settings class CrudServiceInterface(ABC): diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 9bc48da..b0301ac 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -7,11 +7,11 @@ from aciniformes_backend.models import Alert, Fetcher, FetcherType, Receiver from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema +from pinger_backend.service.session import dbsession from pinger_backend.settings import get_settings as backend_settings from .crud import CrudServiceInterface from .exceptions import AlreadyRunning, AlreadyStopped, ConnectionFail -from pinger_backend.service.session import dbsession class SchedulerServiceInterface(ABC): diff --git a/pinger_backend/service/session.py b/pinger_backend/service/session.py index 620a350..04fd392 100644 --- a/pinger_backend/service/session.py +++ b/pinger_backend/service/session.py @@ -1,8 +1,9 @@ -from aciniformes_backend.settings import get_settings as db_settings -from aciniformes_backend.models.base import BaseModel from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker +from aciniformes_backend.models.base import BaseModel +from aciniformes_backend.settings import get_settings as db_settings + def dbsession() -> Session: settings = db_settings() diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 22ab06e..2346231 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,6 +1,7 @@ import pytest from aciniformes_backend.models import Fetcher, Metric +from pinger_backend.service.exceptions import AlreadyRunning @pytest.fixture() @@ -44,9 +45,10 @@ async def test_start_already_started(self, pg_scheduler_service, fake_crud_servi fail = False try: pg_scheduler_service.start() - except: + except AlreadyRunning: fail = True assert fail + pg_scheduler_service.stop() @pytest.mark.asyncio async def test_ping_fail(self, pg_scheduler_service, fetcher_obj, fake_crud_service, dbsession): From b0a072bce020445b79c8dc72a98c0702c3eedbe3 Mon Sep 17 00:00:00 2001 From: Wudext Date: Sat, 27 May 2023 14:46:00 +0300 Subject: [PATCH 30/53] =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f86fb1..f60167b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,8 +30,7 @@ jobs: DB_DSN=postgresql://postgres@localhost:5432/postgres alembic upgrade head - name: Build coverage file run: | - python -m acinifomes_backend - DB_DSN=postgresql://postgres@localhost:5432/postgres pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=auth_backend tests/ | tee pytest-coverage.txt + DB_DSN=postgresql://postgres@localhost:5432/postgres pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered tests/ | tee pytest-coverage.txt - name: Print report if: always() run: | From fc316a4fff7431d98cfae3f0a093859ec60a5edb Mon Sep 17 00:00:00 2001 From: Wudext Date: Sat, 27 May 2023 14:58:14 +0300 Subject: [PATCH 31/53] =?UTF-8?q?=D1=85=D0=B2=D0=B0=D1=82=D0=B8=D1=82=20?= =?UTF-8?q?=D1=81=20=D0=BC=D0=B5=D0=BD=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pinger_backend/__main__.py | 1 - pinger_backend/service/scheduler.py | 6 +++--- tests/ping_service/service/test_scheduler.py | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 9386b1e..37f53c2 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -9,7 +9,6 @@ if __name__ == "__main__": loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) scheduler = ApSchedulerService(CrudService()) scheduler.backend_url = get_settings().BACKEND_URL loop.create_task(scheduler.start()) diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index b0301ac..6baf33d 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -101,14 +101,14 @@ def delete_fetcher(self, fetcher: Fetcher): def get_jobs(self): return [j.id for j in self.scheduler.get_jobs()] - def start(self): + async def start(self): if self.scheduler.running: raise AlreadyRunning fetchers = dbsession().query(Fetcher).all() self.scheduler.start() for fetcher in fetchers: self.add_fetcher(fetcher) - self._fetch_it(fetcher) + await self._fetch_it(fetcher) def stop(self): if not self.scheduler.running: @@ -127,7 +127,7 @@ def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): def _parse_timedelta(fetcher: Fetcher): return fetcher.delay_ok, fetcher.delay_fail - def _fetch_it(self, fetcher: Fetcher): + async def _fetch_it(self, fetcher: Fetcher): prev = time.time() res = None try: diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 2346231..8f217f2 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -41,10 +41,10 @@ async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): @pytest.mark.asyncio async def test_start_already_started(self, pg_scheduler_service, fake_crud_service, crud_client): - pg_scheduler_service.start() + await pg_scheduler_service.start() fail = False try: - pg_scheduler_service.start() + await pg_scheduler_service.start() except AlreadyRunning: fail = True assert fail From b965e9307f840fead6f000af2edf71bd2378183c Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 29 May 2023 16:39:42 +0300 Subject: [PATCH 32/53] Fixing --- aciniformes_backend/routes/alert/alert.py | 5 ----- aciniformes_backend/routes/alert/reciever.py | 5 ----- aciniformes_backend/routes/fetcher.py | 5 ----- aciniformes_backend/serivce/receiver.py | 2 +- tests/backend/service/test_alert_serivce.py | 4 ++-- 5 files changed, 3 insertions(+), 18 deletions(-) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 68bc0ee..94dc3e7 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -41,21 +41,18 @@ async def create( create_schema: CreateSchema, alert: AlertServiceInterface = Depends(alert_service), ): - logger.info(f"Someone triggered create_schema") id_ = await alert.create(create_schema.dict(exclude_unset=True)) return PostResponseSchema(**create_schema.dict(), id=id_) @router.get("") async def get_all(alert: AlertServiceInterface = Depends(alert_service)): - logger.info(f"Someone triggered get_schemas") res = await alert.get_all() return res @router.get("/{id}") async def get(id: int, alert: AlertServiceInterface = Depends(alert_service)): - logger.info(f"Someone triggered get_schema") try: res = await alert.get_by_id(id) except exc.ObjectNotFound: @@ -69,7 +66,6 @@ async def update( update_schema: UpdateSchema, alert: AlertServiceInterface = Depends(alert_service), ): - logger.info(f"Someone triggered update_schema") try: res = await alert.update(id, update_schema.dict(exclude_unset=True)) except exc.ObjectNotFound: @@ -82,5 +78,4 @@ async def delete( id: int, alert: AlertServiceInterface = Depends(alert_service), ): - logger.info(f"Someone triggered delete_schema") await alert.delete(id) diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index 19218df..e467211 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -48,7 +48,6 @@ class GetSchema(BaseModel): @router.post("", response_model=PostResponseSchema) async def create(create_schema: CreateSchema, receiver: ReceiverServiceInterface = Depends(receiver_service)): - logger.info(f"Someone triggered create_receiver") id_ = await receiver.create(create_schema.dict()) return PostResponseSchema(**create_schema.dict(), id=id_) @@ -57,14 +56,12 @@ async def create(create_schema: CreateSchema, receiver: ReceiverServiceInterface async def get_all( receiver: ReceiverServiceInterface = Depends(receiver_service), ): - logger.info(f"Someone triggered get_receivers") res = await receiver.get_all() return res @router.get("/{id}") async def get(id: int, receiver: ReceiverServiceInterface = Depends(receiver_service)): - logger.info(f"Someone triggered get_receiver") try: res = await receiver.get_by_id(id) return res @@ -78,7 +75,6 @@ async def update( update_schema: UpdateSchema, receiver: ReceiverServiceInterface = Depends(receiver_service), ): - logger.info(f"Someone triggered update_receiver") try: res = await receiver.update(id, update_schema.dict(exclude_unset=True)) except exc.ObjectNotFound: @@ -91,5 +87,4 @@ async def delete( id: int, receiver: ReceiverServiceInterface = Depends(receiver_service), ): - logger.info(f"Someone triggered delete_receiver") await receiver.delete(id) diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index b3b2db1..d7eb80f 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -44,7 +44,6 @@ async def create( create_schema: CreateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): - logger.info(f"Someone triggered create_fetcher") id_ = await fetcher.create(create_schema.dict()) return ResponsePostSchema(**create_schema.dict(), id=id_) @@ -53,7 +52,6 @@ async def create( async def get_all( fetcher: FetcherServiceInterface = Depends(fetcher_service), ): - logger.info(f"Someone triggered get_fetchers") res = await fetcher.get_all() return res @@ -63,7 +61,6 @@ async def get( id: int, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): - logger.info(f"Someone triggered get_fetcher") try: res = await fetcher.get_by_id(id) except exc.ObjectNotFound: @@ -77,7 +74,6 @@ async def update( update_schema: UpdateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): - logger.info(f"Someone triggered update_fetcher") try: res = await fetcher.update(id, update_schema.dict(exclude_unset=True)) except exc.ObjectNotFound: @@ -90,5 +86,4 @@ async def delete( id: int, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): - logger.info(f"Someone triggered delete_fetcher") await fetcher.delete(id) diff --git a/aciniformes_backend/serivce/receiver.py b/aciniformes_backend/serivce/receiver.py index 51c711e..494e9ab 100644 --- a/aciniformes_backend/serivce/receiver.py +++ b/aciniformes_backend/serivce/receiver.py @@ -15,7 +15,7 @@ async def create(self, item: dict) -> int: self.session.flush() return receiver.id_ - def get_by_id(self, id_: int) -> Type[db_models.Receiver]: + async def get_by_id(self, id_: int) -> Type[db_models.Receiver]: q = sa.select(db_models.Receiver).where(db_models.Receiver.id_ == id_) res = self.session.scalar(q) if not res: diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index 3826fdd..13816c7 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -66,11 +66,11 @@ async def test_get_all(self, pg_receiver_service, db_receiver, db_alert): @pytest.mark.asyncio async def test_get_by_id(self, pg_receiver_service, db_receiver): - res = pg_receiver_service.get_by_id(db_receiver.id_) + res = await pg_receiver_service.get_by_id(db_receiver.id_) assert res is not None assert res.url == db_receiver.url with pytest.raises(exc.ObjectNotFound): - pg_receiver_service.get_by_id(db_receiver.id_ + 1000) + await pg_receiver_service.get_by_id(db_receiver.id_ + 1000) @pytest.mark.asyncio async def test_delete(self, pg_receiver_service, db_receiver): From b5ca78b852333576b542b21cb05d135700845ec2 Mon Sep 17 00:00:00 2001 From: Wudext Date: Mon, 29 May 2023 16:44:31 +0300 Subject: [PATCH 33/53] Docker + makefile --- Dockerfile | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b7221b5..5fc2113 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 ARG APP_VERSION=dev ENV APP_VERSION=${APP_VERSION} -ENV APP_NAME=pinger_backend +ENV APP_NAME=aciniformes_backend ENV APP_MODULE=${APP_NAME}.routes.base:app COPY ./requirements.txt /app/ diff --git a/Makefile b/Makefile index 61686fe..bda42fb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ run: - source ./venv/bin/activate && uvicorn --reload --log-config logging_dev.conf pinger_backend.routes.base:app + source ./venv/bin/activate && uvicorn --reload --log-config logging_dev.conf aciniformes_backend.routes.base:app configure: venv source ./venv/bin/activate && pip install -r requirements.dev.txt -r requirements.txt From a63573bd9fee4db5558304fa1b90679ac0cf85f8 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Thu, 6 Jul 2023 18:57:04 +0300 Subject: [PATCH 34/53] Fix session + delete abstract classes + change metric/alert logic --- .idea/workspace.xml | 93 ++++++++++++++++++++++ pinger_backend/service/__init__.py | 8 +- pinger_backend/service/bootstrap.py | 8 +- pinger_backend/service/crud.py | 51 ++---------- pinger_backend/service/scheduler.py | 115 ++++++++-------------------- pinger_backend/service/session.py | 6 +- requirements.txt | 2 +- 7 files changed, 143 insertions(+), 140 deletions(-) create mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..ec1400c --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1685204231310 + + + + + + + + \ No newline at end of file diff --git a/pinger_backend/service/__init__.py b/pinger_backend/service/__init__.py index 076add7..fccc6bb 100644 --- a/pinger_backend/service/__init__.py +++ b/pinger_backend/service/__init__.py @@ -1,11 +1,11 @@ from .bootstrap import Config, crud_service, scheduler_service -from .crud import CrudServiceInterface -from .scheduler import SchedulerServiceInterface +from .crud import CrudService +from .scheduler import ApSchedulerService __all__ = [ - "CrudServiceInterface", - "SchedulerServiceInterface", + "CrudService", + "ApSchedulerService", "crud_service", "scheduler_service", "Config", diff --git a/pinger_backend/service/bootstrap.py b/pinger_backend/service/bootstrap.py index 5cc87c0..6906563 100644 --- a/pinger_backend/service/bootstrap.py +++ b/pinger_backend/service/bootstrap.py @@ -1,5 +1,5 @@ -from .crud import CrudService, FakeCrudService -from .scheduler import ApSchedulerService, FakeSchedulerService +from .crud import CrudService +from .scheduler import ApSchedulerService class Config: @@ -7,8 +7,8 @@ class Config: def crud_service(): - return FakeCrudService() if Config.fake else CrudService() + return CrudService() def scheduler_service(): - return FakeSchedulerService(crud_service()) if Config.fake else ApSchedulerService(crud_service()) + return ApSchedulerService(crud_service()) diff --git a/pinger_backend/service/crud.py b/pinger_backend/service/crud.py index 1472ae1..18d428a 100644 --- a/pinger_backend/service/crud.py +++ b/pinger_backend/service/crud.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod +from abc import ABC from aciniformes_backend.models import Alert, Fetcher, Metric from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema @@ -6,21 +6,7 @@ from pinger_backend.settings import get_settings -class CrudServiceInterface(ABC): - @abstractmethod - def get_fetchers(self) -> list[Fetcher]: - raise NotImplementedError - - @abstractmethod - def add_metric(self, metric: MetricCreateSchema): - raise NotImplementedError - - @abstractmethod - def get_alerts(self) -> list[Alert]: - raise NotImplementedError - - -class CrudService(CrudServiceInterface): +class CrudService(ABC): backend_url: str def __init__(self): @@ -30,37 +16,12 @@ def get_fetchers(self) -> list[Fetcher]: return [Fetcher(**d) for d in dbsession().query(Fetcher).all()] def add_metric(self, metric: MetricCreateSchema): + session = dbsession() metric = Metric(**metric.dict(exclude_none=True)) - dbsession().add(metric) - dbsession().commit() - dbsession().flush() + session.add(metric) + session.commit() + session.flush() return metric def get_alerts(self) -> list[Alert]: return [Alert(**d) for d in dbsession().query(Alert).all()] - - -class FakeCrudService(CrudServiceInterface): - fetcher_repo: dict[int, Fetcher] = { - 0: Fetcher( - **{ - "type_": "get_ok", - "address": "https://www.python.org", - "fetch_data": None, - "delay_ok": 30, - "delay_fail": 40, - } - ) - } - alert_repo: dict[int, Alert] = dict() - metric_repo: dict[int, MetricCreateSchema] = dict() - - def get_fetchers(self) -> list[Fetcher]: - return list(self.fetcher_repo.values()) - - def add_metric(self, metric: MetricCreateSchema): - self.metric_repo[self.id_incr] = metric - self.id_incr += 1 - - def get_alerts(self) -> list[Alert]: - return list(self.alert_repo.values()) diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 6baf33d..04d92f1 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -1,89 +1,22 @@ import time -from abc import ABC, abstractmethod +from abc import ABC -import httpx -from apscheduler.schedulers.asyncio import AsyncIOScheduler, BaseScheduler - -from aciniformes_backend.models import Alert, Fetcher, FetcherType, Receiver +import requests +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from aciniformes_backend.models import Fetcher, FetcherType, Receiver, Alert, Metric from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from pinger_backend.service.session import dbsession -from pinger_backend.settings import get_settings as backend_settings - -from .crud import CrudServiceInterface -from .exceptions import AlreadyRunning, AlreadyStopped, ConnectionFail - - -class SchedulerServiceInterface(ABC): - crud_service: CrudServiceInterface - scheduler: BaseScheduler | dict - - @abstractmethod - async def add_fetcher(self, fetcher: Fetcher): - raise NotImplementedError - - @abstractmethod - async def delete_fetcher(self, fetcher: Fetcher): - raise NotImplementedError - - @abstractmethod - async def get_jobs(self): - raise NotImplementedError - - @abstractmethod - async def start(self): - raise NotImplementedError - - @abstractmethod - async def stop(self): - raise NotImplementedError - - @abstractmethod - async def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - raise NotImplementedError - - async def _add_metric(self, metric: MetricCreateSchema): - await self.crud_service.add_metric(metric) - - @property - def alerts(self): - return self.crud_service.get_alerts() - - -class FakeSchedulerService(SchedulerServiceInterface): - scheduler = dict() - - def __init__(self, crud_service: CrudServiceInterface): - self.crud_service = crud_service - - def add_fetcher(self, fetcher: Fetcher): - self.scheduler[fetcher.id_] = fetcher - - def delete_fetcher(self, fetcher: Fetcher): - del self.scheduler[fetcher.id_] - - def get_jobs(self): - return self.crud_service.get_fetchers() - - def start(self): - if "started" in self.scheduler: - raise AlreadyRunning - self.scheduler["started"] = True - - def stop(self): - if not self.scheduler["started"]: - raise AlreadyStopped - self.scheduler["started"] = False - def write_alert(self, metric_log: MetricCreateSchema, alert: Alert): - httpx.post(f"{backend_settings.BOT_URL}/alert", json=metric_log.json()) +from .crud import CrudService +from .exceptions import AlreadyRunning, AlreadyStopped -class ApSchedulerService(SchedulerServiceInterface): +class ApSchedulerService(ABC): scheduler = AsyncIOScheduler() backend_url = str - def __init__(self, crud_service: CrudServiceInterface): + def __init__(self, crud_service: CrudService): self.crud_service = crud_service def add_fetcher(self, fetcher: Fetcher): @@ -119,9 +52,14 @@ def stop(self): def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): receivers = dbsession().query(Receiver).all() + session = dbsession() + alert = Alert(**alert.dict(exclude_none=True)) + session.add(alert) + session.commit() + session.flush() for receiver in receivers: receiver.receiver_body['text'] = metric_log - httpx.post(receiver.url, data=receiver['receiver_body']) + requests.request(method="POST", url=receiver.url, data=receiver['receiver_body']) @staticmethod def _parse_timedelta(fetcher: Fetcher): @@ -133,32 +71,43 @@ async def _fetch_it(self, fetcher: Fetcher): try: match fetcher.type_: case FetcherType.GET: - res = httpx.get(fetcher.address) + res = requests.request(method="GET", url=fetcher.address) case FetcherType.POST: - res = httpx.post(fetcher.address, data=fetcher.fetch_data) + res = requests.request(method="POST", url=fetcher.address, data=fetcher.fetch_data) case FetcherType.PING: - res = httpx.head(fetcher.address) + res = requests.request(method="HEAD", url=fetcher.address) except: cur = time.time() timing = cur - prev metric = MetricCreateSchema( - name=fetcher.address, ok=True if res and res.status_code == 200 else False, time_delta=timing + name=fetcher.address, ok=True if res and (200 <= res.status_code <= 300) else False, time_delta=timing ) - self.crud_service.add_metric(metric) + if metric.name not in [item.name for item in dbsession().query(Metric).all()]: + self.crud_service.add_metric(metric) + else: + if metric.ok != dbsession().query(Metric).filter(Metric.name == metric.name).one_or_none().ok: + dbsession().query(Metric).filter(Metric.name == metric.name).delete() + self.crud_service.add_metric(metric) alert = AlertCreateSchema(data=metric, filter=500) + if alert.data["name"] not in [item.data["name"] for item in dbsession().query(Alert).all()]: + self.write_alert(metric, alert) self.scheduler.reschedule_job( f"{fetcher.address} {fetcher.create_ts}", seconds=fetcher.delay_fail, trigger="interval", ) - self.write_alert(metric, alert) return cur = time.time() timing = cur - prev metric = MetricCreateSchema( name=fetcher.address, ok=True if res and (200 <= res.status_code <= 300) else False, time_delta=timing ) - self.crud_service.add_metric(metric) + if metric.name not in [item.name for item in dbsession().query(Metric).all()]: + self.crud_service.add_metric(metric) + else: + if metric.ok != dbsession().query(Metric).filter(Metric.name == metric.name).one_or_none().ok: + dbsession().query(Metric).filter(Metric.name == metric.name).delete() + self.crud_service.add_metric(metric) if not metric.ok: alert = AlertCreateSchema(data=metric, filter=res.status_code) self.scheduler.reschedule_job( diff --git a/pinger_backend/service/session.py b/pinger_backend/service/session.py index 04fd392..f60072f 100644 --- a/pinger_backend/service/session.py +++ b/pinger_backend/service/session.py @@ -1,7 +1,6 @@ from sqlalchemy import create_engine -from sqlalchemy.orm import Session, sessionmaker +from sqlalchemy.orm import Session, sessionmaker, declarative_base -from aciniformes_backend.models.base import BaseModel from aciniformes_backend.settings import get_settings as db_settings @@ -9,7 +8,8 @@ def dbsession() -> Session: settings = db_settings() engine = create_engine(settings.DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) session = sessionmaker(bind=engine) + Base = declarative_base() localsession = session() - BaseModel.metadata.create_all(bind=engine) + Base.metadata.create_all(engine) return localsession diff --git a/requirements.txt b/requirements.txt index 9e9a81c..eeceacc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,4 @@ APScheduler jose auth-lib-profcomff asyncio -httpx \ No newline at end of file +requests \ No newline at end of file From 518e3bf49b727a416fdd636aa8b8b4f300e41927 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Thu, 6 Jul 2023 18:58:54 +0300 Subject: [PATCH 35/53] Black + isort --- aciniformes_backend/routes/alert/alert.py | 4 ++-- pinger_backend/service/scheduler.py | 3 ++- pinger_backend/service/session.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 94dc3e7..e77cb8c 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -13,7 +13,7 @@ class CreateSchema(BaseModel): - data: dict + data: dict[str, str] filter: str @@ -22,7 +22,7 @@ class PostResponseSchema(CreateSchema): class UpdateSchema(BaseModel): - data: dict | None + data: dict[str, str] | None filter: str | None diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 04d92f1..82608d8 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -3,7 +3,8 @@ import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler -from aciniformes_backend.models import Fetcher, FetcherType, Receiver, Alert, Metric + +from aciniformes_backend.models import Alert, Fetcher, FetcherType, Metric, Receiver from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from pinger_backend.service.session import dbsession diff --git a/pinger_backend/service/session.py b/pinger_backend/service/session.py index f60072f..bce4818 100644 --- a/pinger_backend/service/session.py +++ b/pinger_backend/service/session.py @@ -1,5 +1,5 @@ from sqlalchemy import create_engine -from sqlalchemy.orm import Session, sessionmaker, declarative_base +from sqlalchemy.orm import Session, declarative_base, sessionmaker from aciniformes_backend.settings import get_settings as db_settings From cb41ea83a2f9021036e7d1138c577ea3aefa07a4 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Tue, 11 Jul 2023 11:27:54 +0300 Subject: [PATCH 36/53] Check for addition/deletion while working --- pinger_backend/service/scheduler.py | 43 +++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 82608d8..6b6a95a 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -16,6 +16,7 @@ class ApSchedulerService(ABC): scheduler = AsyncIOScheduler() backend_url = str + fetchers = set def __init__(self, crud_service: CrudService): self.crud_service = crud_service @@ -38,9 +39,9 @@ def get_jobs(self): async def start(self): if self.scheduler.running: raise AlreadyRunning - fetchers = dbsession().query(Fetcher).all() + self.fetchers = dbsession().query(Fetcher).all() self.scheduler.start() - for fetcher in fetchers: + for fetcher in self.fetchers: self.add_fetcher(fetcher) await self._fetch_it(fetcher) @@ -97,6 +98,25 @@ async def _fetch_it(self, fetcher: Fetcher): seconds=fetcher.delay_fail, trigger="interval", ) + + jobs = [job.id for job in self.scheduler.get_jobs()] + old_fetchers = self.fetchers + new_fetchers = dbsession().query(Fetcher).all() + + # Проверка на удаление фетчера + for fetcher in old_fetchers: + if (fetcher.address not in [ftch.address for ftch in new_fetchers]) and ( + f"{fetcher.address} {fetcher.create_ts}" in jobs): + self.scheduler.remove_job(job_id=f"{fetcher.address} {fetcher.create_ts}") + + jobs = [job.id for job in self.scheduler.get_jobs()] + # Проверка на добавление нового фетчера + for fetcher in new_fetchers: + if (f"{fetcher.address} {fetcher.create_ts}" not in jobs) and ( + fetcher.address not in [ftch.address for ftch in old_fetchers]): + self.add_fetcher(fetcher) + self.fetchers.append(fetcher) + return cur = time.time() timing = cur - prev @@ -111,15 +131,32 @@ async def _fetch_it(self, fetcher: Fetcher): self.crud_service.add_metric(metric) if not metric.ok: alert = AlertCreateSchema(data=metric, filter=res.status_code) + if alert.data["name"] not in [item.data["name"] for item in dbsession().query(Alert).all()]: + self.write_alert(metric, alert) self.scheduler.reschedule_job( f"{fetcher.address} {fetcher.create_ts}", seconds=fetcher.delay_fail, trigger="interval", ) - self.write_alert(metric, alert) else: self.scheduler.reschedule_job( f"{fetcher.address} {fetcher.create_ts}", seconds=fetcher.delay_ok, trigger="interval", ) + + jobs = [job.id for job in self.scheduler.get_jobs()] + old_fetchers = self.fetchers + new_fetchers = dbsession().query(Fetcher).all() + + # Проверка на удаление фетчера + for fetcher in old_fetchers: + if (fetcher.address not in [ftch.address for ftch in new_fetchers]) and (f"{fetcher.address} {fetcher.create_ts}" in jobs): + self.scheduler.remove_job(job_id=f"{fetcher.address} {fetcher.create_ts}") + + # Проверка на добавление нового фетчера + jobs = [job.id for job in self.scheduler.get_jobs()] + for fetcher in new_fetchers: + if (f"{fetcher.address} {fetcher.create_ts}" not in jobs) and (fetcher.address not in [ftch.address for ftch in old_fetchers]): + self.add_fetcher(fetcher) + self.fetchers.append(fetcher) From a211f88e589c3d592fe912c5f9f1644881b0af49 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Tue, 11 Jul 2023 11:28:20 +0300 Subject: [PATCH 37/53] =?UTF-8?q?Black=20+=20Isort=20(again=20=D0=B1=D0=BB?= =?UTF-8?q?=D1=8F=D1=82=D1=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pinger_backend/service/scheduler.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 6b6a95a..61501c1 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -106,14 +106,16 @@ async def _fetch_it(self, fetcher: Fetcher): # Проверка на удаление фетчера for fetcher in old_fetchers: if (fetcher.address not in [ftch.address for ftch in new_fetchers]) and ( - f"{fetcher.address} {fetcher.create_ts}" in jobs): + f"{fetcher.address} {fetcher.create_ts}" in jobs + ): self.scheduler.remove_job(job_id=f"{fetcher.address} {fetcher.create_ts}") jobs = [job.id for job in self.scheduler.get_jobs()] # Проверка на добавление нового фетчера for fetcher in new_fetchers: if (f"{fetcher.address} {fetcher.create_ts}" not in jobs) and ( - fetcher.address not in [ftch.address for ftch in old_fetchers]): + fetcher.address not in [ftch.address for ftch in old_fetchers] + ): self.add_fetcher(fetcher) self.fetchers.append(fetcher) @@ -151,12 +153,16 @@ async def _fetch_it(self, fetcher: Fetcher): # Проверка на удаление фетчера for fetcher in old_fetchers: - if (fetcher.address not in [ftch.address for ftch in new_fetchers]) and (f"{fetcher.address} {fetcher.create_ts}" in jobs): + if (fetcher.address not in [ftch.address for ftch in new_fetchers]) and ( + f"{fetcher.address} {fetcher.create_ts}" in jobs + ): self.scheduler.remove_job(job_id=f"{fetcher.address} {fetcher.create_ts}") # Проверка на добавление нового фетчера jobs = [job.id for job in self.scheduler.get_jobs()] for fetcher in new_fetchers: - if (f"{fetcher.address} {fetcher.create_ts}" not in jobs) and (fetcher.address not in [ftch.address for ftch in old_fetchers]): + if (f"{fetcher.address} {fetcher.create_ts}" not in jobs) and ( + fetcher.address not in [ftch.address for ftch in old_fetchers] + ): self.add_fetcher(fetcher) self.fetchers.append(fetcher) From 2f4d7cb0311d8624598eba4716b7b5aaab00df16 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Tue, 11 Jul 2023 11:36:01 +0300 Subject: [PATCH 38/53] Pydantic 2 + fix requirements.txt --- .idea/aciniformes-project.iml | 10 ++++++++++ aciniformes_backend/settings.py | 3 ++- requirements.txt | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .idea/aciniformes-project.iml diff --git a/.idea/aciniformes-project.iml b/.idea/aciniformes-project.iml new file mode 100644 index 0000000..aad402c --- /dev/null +++ b/.idea/aciniformes-project.iml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/aciniformes_backend/settings.py b/aciniformes_backend/settings.py index 557b2ba..8f40353 100644 --- a/aciniformes_backend/settings.py +++ b/aciniformes_backend/settings.py @@ -1,6 +1,7 @@ from functools import lru_cache -from pydantic import BaseSettings, PostgresDsn +from pydantic import PostgresDsn +from pydantic_settings import BaseSettings class Settings(BaseSettings): diff --git a/requirements.txt b/requirements.txt index eeceacc..07e7b8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ fastapi sqlalchemy pydantic +pydantic-settings uvicorn alembic python-dotenv From 6b87bba9d5b0e054c60a7b1e6ee2966effb1f511 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Tue, 11 Jul 2023 12:07:26 +0300 Subject: [PATCH 39/53] Fix migrations --- aciniformes_backend/routes/alert/alert.py | 2 +- aciniformes_backend/routes/base.py | 2 +- aciniformes_backend/serivce/base.py | 1 - aciniformes_backend/settings.py | 9 ++------- migrations/env.py | 3 +-- pinger_backend/service/scheduler.py | 4 ++-- pinger_backend/service/session.py | 2 +- pinger_backend/settings.py | 10 +++------- tests/backend/api/test_alert.py | 4 +--- tests/backend/conftest.py | 2 +- tests/ping_service/conftest.py | 2 +- 11 files changed, 14 insertions(+), 27 deletions(-) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index e77cb8c..d0f2db7 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -13,7 +13,7 @@ class CreateSchema(BaseModel): - data: dict[str, str] + data: dict filter: str diff --git a/aciniformes_backend/routes/base.py b/aciniformes_backend/routes/base.py index ca84348..008e58e 100644 --- a/aciniformes_backend/routes/base.py +++ b/aciniformes_backend/routes/base.py @@ -17,6 +17,6 @@ app.add_middleware( DBSessionMiddleware, - db_url=get_settings().DB_DSN, + db_url=str(get_settings().DB_DSN), engine_args={"pool_pre_ping": True, "isolation_level": "AUTOCOMMIT"}, ) diff --git a/aciniformes_backend/serivce/base.py b/aciniformes_backend/serivce/base.py index df2dd68..d48596a 100644 --- a/aciniformes_backend/serivce/base.py +++ b/aciniformes_backend/serivce/base.py @@ -1,6 +1,5 @@ from abc import ABC, abstractmethod -import pydantic import sqlalchemy.orm import aciniformes_backend.models as db_models diff --git a/aciniformes_backend/settings.py b/aciniformes_backend/settings.py index 8f40353..95e3cd1 100644 --- a/aciniformes_backend/settings.py +++ b/aciniformes_backend/settings.py @@ -1,17 +1,12 @@ from functools import lru_cache -from pydantic import PostgresDsn +from pydantic import ConfigDict, PostgresDsn from pydantic_settings import BaseSettings class Settings(BaseSettings): DB_DSN: PostgresDsn - - class Config: - """Pydantic BaseSettings config""" - - case_sensitive = True - env_file = ".env" + model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") @lru_cache() diff --git a/migrations/env.py b/migrations/env.py index 867c241..596fc2e 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -10,7 +10,6 @@ config = context.config settings = get_settings() - if config.config_file_name is not None: fileConfig(config.config_file_name) @@ -49,7 +48,7 @@ def run_migrations_online() -> None: """ configuration = config.get_section(config.config_ini_section) - configuration["sqlalchemy.url"] = settings.DB_DSN + configuration["sqlalchemy.url"] = str(settings.DB_DSN) connectable = engine_from_config( configuration, prefix="sqlalchemy.", diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 61501c1..1b6d6cb 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -55,7 +55,7 @@ def stop(self): def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): receivers = dbsession().query(Receiver).all() session = dbsession() - alert = Alert(**alert.dict(exclude_none=True)) + alert = Alert(**alert.model_dump(exclude_none=True)) session.add(alert) session.commit() session.flush() @@ -90,7 +90,7 @@ async def _fetch_it(self, fetcher: Fetcher): if metric.ok != dbsession().query(Metric).filter(Metric.name == metric.name).one_or_none().ok: dbsession().query(Metric).filter(Metric.name == metric.name).delete() self.crud_service.add_metric(metric) - alert = AlertCreateSchema(data=metric, filter=500) + alert = AlertCreateSchema(data=metric.model_dump(), filter='500') if alert.data["name"] not in [item.data["name"] for item in dbsession().query(Alert).all()]: self.write_alert(metric, alert) self.scheduler.reschedule_job( diff --git a/pinger_backend/service/session.py b/pinger_backend/service/session.py index bce4818..f8c0a81 100644 --- a/pinger_backend/service/session.py +++ b/pinger_backend/service/session.py @@ -6,7 +6,7 @@ def dbsession() -> Session: settings = db_settings() - engine = create_engine(settings.DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) + engine = create_engine(str(settings.DB_DSN), execution_options={"isolation_level": "AUTOCOMMIT"}) session = sessionmaker(bind=engine) Base = declarative_base() localsession = session() diff --git a/pinger_backend/settings.py b/pinger_backend/settings.py index cac47e4..cee18c0 100644 --- a/pinger_backend/settings.py +++ b/pinger_backend/settings.py @@ -1,17 +1,13 @@ from functools import lru_cache -from pydantic import BaseSettings, HttpUrl +from pydantic import ConfigDict, HttpUrl +from pydantic_settings import BaseSettings class Settings(BaseSettings): BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" BOT_URL: HttpUrl = "http://127.0.0.1:8001" - - class Config: - """Pydantic BaseSettings config""" - - case_sensitive = True - env_file = ".env" + model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") @lru_cache() diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 7945fb7..5ab6bbe 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -64,9 +64,7 @@ def test_get_success(self, client, this_alert): assert len(res_body) def test_patch_by_id_success(self, client, this_alert): - body = { - "data": {"type": "g", "name": "s"}, - } + body = {"data": {"name": "g", "ok": True, "time_delta": 0.1}, "filter": 200} res = client.patch(f"{self._url}/{this_alert['id']}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index c4be8fd..0ad6343 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -12,7 +12,7 @@ @pytest.fixture(scope="session") def engine(): - return create_engine(get_settings().DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) + return create_engine(str(get_settings().DB_DSN), execution_options={"isolation_level": "AUTOCOMMIT"}) @pytest.fixture(scope="session") diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index 6a3a5d2..4783bb3 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -25,7 +25,7 @@ def crud_client(): @pytest.fixture(scope="session") def dbsession() -> Session: settings = get_settings() - engine = create_engine(settings.DB_DSN, execution_options={"isolation_level": "AUTOCOMMIT"}) + engine = create_engine(str(settings.DB_DSN), execution_options={"isolation_level": "AUTOCOMMIT"}) TestingSessionLocal = sessionmaker(bind=engine) BaseModel.metadata.drop_all(bind=engine) BaseModel.metadata.create_all(bind=engine) From eb079dabbeb6150303a22925d41da9e2c88d4ca8 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Sat, 15 Jul 2023 19:47:11 +0300 Subject: [PATCH 40/53] Delete .idea directory --- .idea/aciniformes-project.iml | 10 ---- .idea/workspace.xml | 93 ----------------------------------- 2 files changed, 103 deletions(-) delete mode 100644 .idea/aciniformes-project.iml delete mode 100644 .idea/workspace.xml diff --git a/.idea/aciniformes-project.iml b/.idea/aciniformes-project.iml deleted file mode 100644 index aad402c..0000000 --- a/.idea/aciniformes-project.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index ec1400c..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1685204231310 - - - - - - - - \ No newline at end of file From 5be400ebd9222556d1f8c280db6266cbab9c1c9d Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Sat, 15 Jul 2023 20:09:59 +0300 Subject: [PATCH 41/53] Alert and Receiver schema fix + test fix --- aciniformes_backend/routes/alert/alert.py | 4 ++-- aciniformes_backend/routes/alert/reciever.py | 8 ++++---- tests/backend/api/test_alert.py | 10 +++++++--- tests/backend/api/test_fetcher.py | 8 +++++++- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index d0f2db7..524ea7d 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -13,7 +13,7 @@ class CreateSchema(BaseModel): - data: dict + data: dict[str, str | list | dict] filter: str @@ -22,7 +22,7 @@ class PostResponseSchema(CreateSchema): class UpdateSchema(BaseModel): - data: dict[str, str] | None + data: dict[str, str | list | dict] | None filter: str | None diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index e467211..040218f 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -22,25 +22,25 @@ class Method(str, enum.Enum): class CreateSchema(BaseModel): url: str method: Method - receiver_body: dict + receiver_body: dict[str, str | int | list] class PostResponseSchema(CreateSchema): url: str | None method: Method - receiver_body: dict | None + receiver_body: dict[str, str | int | list] | None class UpdateSchema(BaseModel): url: str | None method: Method | None - receiver_body: dict | None + receiver_body: dict[str, str | int | list] | None class GetSchema(BaseModel): url: str method: Method - receiver_body: dict + receiver_body: dict[str, str | int | list] router = APIRouter() diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 5ab6bbe..769903e 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -64,7 +64,10 @@ def test_get_success(self, client, this_alert): assert len(res_body) def test_patch_by_id_success(self, client, this_alert): - body = {"data": {"name": "g", "ok": True, "time_delta": 0.1}, "filter": 200} + body = { + "data": {"type": "string", "name": "string"}, + "filter": "string", + } res = client.patch(f"{self._url}/{this_alert['id']}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() @@ -77,7 +80,8 @@ def test_get_by_id_not_found(self, client, this_alert): def test_patch_by_id_not_found(self, client, this_alert): body = { - "data": {}, + "data": {"type": "string", "name": "string"}, + "filter": "string", } res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND @@ -136,6 +140,6 @@ def test_get_by_id_not_found(self, client): assert res.status_code == status.HTTP_404_NOT_FOUND def test_patch_by_id_not_found(self, client): - body = {"name": "st", "chat_id": 0} + body = {"url": "sdasd", "method": "post", "receiver_body": {}} res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 125c261..9dcff30 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -81,6 +81,12 @@ def test_get_by_id_not_found(self, client): assert res.status_code == status.HTTP_404_NOT_FOUND def test_patch_by_id_not_found(self, client): - body = {"name": "s"} + body = { + "type_": "post", + "address": "https://api.test.profcomff.com/services/category", + "fetch_data": "string", + "delay_ok": 300, + "delay_fail": 30, + } res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND From 0512d8854f21fc0293ec744c0dfbb156fd9a510d Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Sun, 16 Jul 2023 22:47:37 +0300 Subject: [PATCH 42/53] Dockerfile for pinger + ping fix + update requirements.txt --- Dockerfile => aciniformes_backend/Dockerfile | 0 pinger_backend/Dockerfile | 13 ++++++++++++ pinger_backend/service/scheduler.py | 12 ++++++++--- requirements.txt | 21 ++++++++++---------- 4 files changed, 33 insertions(+), 13 deletions(-) rename Dockerfile => aciniformes_backend/Dockerfile (100%) create mode 100644 pinger_backend/Dockerfile diff --git a/Dockerfile b/aciniformes_backend/Dockerfile similarity index 100% rename from Dockerfile rename to aciniformes_backend/Dockerfile diff --git a/pinger_backend/Dockerfile b/pinger_backend/Dockerfile new file mode 100644 index 0000000..a4bf82c --- /dev/null +++ b/pinger_backend/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3 +ARG APP_VERSION=dev +ENV APP_VERSION=${APP_VERSION} +ENV APP_NAME=pinger_backend +ENV APP_MODULE=${APP_NAME}.service.scheduler:ApSchedulerService().start() + +COPY ./requirements.txt /app/ +COPY ./logging_prod.conf /app/ +COPY ./logging_test.conf /app/ + +RUN pip install -U -r /app/requirements.txt + +COPY ./${APP_NAME} /app/${APP_NAME} diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 1b6d6cb..c6339bb 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -1,6 +1,7 @@ import time from abc import ABC +import ping3 import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler @@ -77,12 +78,15 @@ async def _fetch_it(self, fetcher: Fetcher): case FetcherType.POST: res = requests.request(method="POST", url=fetcher.address, data=fetcher.fetch_data) case FetcherType.PING: - res = requests.request(method="HEAD", url=fetcher.address) + res = ping3.ping(fetcher.address) + except: cur = time.time() timing = cur - prev metric = MetricCreateSchema( - name=fetcher.address, ok=True if res and (200 <= res.status_code <= 300) else False, time_delta=timing + name=fetcher.address, + ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, + time_delta=timing, ) if metric.name not in [item.name for item in dbsession().query(Metric).all()]: self.crud_service.add_metric(metric) @@ -123,7 +127,9 @@ async def _fetch_it(self, fetcher: Fetcher): cur = time.time() timing = cur - prev metric = MetricCreateSchema( - name=fetcher.address, ok=True if res and (200 <= res.status_code <= 300) else False, time_delta=timing + name=fetcher.address, + ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, + time_delta=timing, ) if metric.name not in [item.name for item in dbsession().query(Metric).all()]: self.crud_service.add_metric(metric) diff --git a/requirements.txt b/requirements.txt index 07e7b8e..f467b5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,20 @@ -fastapi -sqlalchemy -pydantic +fastapi~=0.100.0 +sqlalchemy~=2.0.18 +pydantic~=2.0.2 pydantic-settings -uvicorn -alembic +uvicorn~=0.22.0 +alembic~=1.11.1 python-dotenv fastapi-sqlalchemy psycopg2-binary gunicorn -starlette -pytest +starlette~=0.27.0 +pytest~=7.4.0 python-jose python-multipart -APScheduler +APScheduler~=3.10.1 jose auth-lib-profcomff -asyncio -requests \ No newline at end of file +asyncio~=3.4.3 +requests~=2.31.0 +ping3 \ No newline at end of file From d17dbd286e80e31573ec664ea8c84038778aa56d Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Fri, 21 Jul 2023 19:04:53 +0300 Subject: [PATCH 43/53] Dockerfile fix + local startup fix --- aciniformes_backend/Dockerfile | 4 +- aciniformes_backend/__init__.pyc | Bin 118 -> 0 bytes pinger_backend/Dockerfile | 5 +- pinger_backend/__main__.py | 9 +- pinger_backend/models/__init__.py | 7 ++ pinger_backend/models/alerts.py | 31 +++++ pinger_backend/models/base.py | 16 +++ pinger_backend/models/fetcher.py | 30 +++++ pinger_backend/models/metric.py | 16 +++ pinger_backend/routes/__init__.py | 0 pinger_backend/routes/alert/__init__.py | 0 pinger_backend/routes/alert/alert.py | 86 +++++++++++++ pinger_backend/routes/alert/reciever.py | 90 ++++++++++++++ pinger_backend/routes/base.py | 22 ++++ pinger_backend/routes/fetcher.py | 89 ++++++++++++++ pinger_backend/routes/mectric.py | 58 +++++++++ pinger_backend/service/crud.py | 16 ++- pinger_backend/service/scheduler.py | 15 ++- pinger_backend/service/session.py | 8 +- pinger_backend/service/settings.py | 19 +++ pinger_backend/service_backend/__init__.py | 22 ++++ pinger_backend/service_backend/alert.py | 43 +++++++ pinger_backend/service_backend/base.py | 71 +++++++++++ pinger_backend/service_backend/bootstrap.py | 39 ++++++ pinger_backend/service_backend/exceptions.py | 23 ++++ pinger_backend/service_backend/fake.py | 121 +++++++++++++++++++ pinger_backend/service_backend/fetcher.py | 42 +++++++ pinger_backend/service_backend/metric.py | 30 +++++ pinger_backend/service_backend/receiver.py | 48 ++++++++ pinger_backend/settings.py | 11 +- tests/backend/conftest.py | 2 +- 31 files changed, 949 insertions(+), 24 deletions(-) delete mode 100644 aciniformes_backend/__init__.pyc create mode 100644 pinger_backend/models/__init__.py create mode 100644 pinger_backend/models/alerts.py create mode 100644 pinger_backend/models/base.py create mode 100644 pinger_backend/models/fetcher.py create mode 100644 pinger_backend/models/metric.py create mode 100644 pinger_backend/routes/__init__.py create mode 100644 pinger_backend/routes/alert/__init__.py create mode 100644 pinger_backend/routes/alert/alert.py create mode 100644 pinger_backend/routes/alert/reciever.py create mode 100644 pinger_backend/routes/base.py create mode 100644 pinger_backend/routes/fetcher.py create mode 100644 pinger_backend/routes/mectric.py create mode 100644 pinger_backend/service/settings.py create mode 100644 pinger_backend/service_backend/__init__.py create mode 100644 pinger_backend/service_backend/alert.py create mode 100644 pinger_backend/service_backend/base.py create mode 100644 pinger_backend/service_backend/bootstrap.py create mode 100644 pinger_backend/service_backend/exceptions.py create mode 100644 pinger_backend/service_backend/fake.py create mode 100644 pinger_backend/service_backend/fetcher.py create mode 100644 pinger_backend/service_backend/metric.py create mode 100644 pinger_backend/service_backend/receiver.py diff --git a/aciniformes_backend/Dockerfile b/aciniformes_backend/Dockerfile index 5fc2113..7069106 100644 --- a/aciniformes_backend/Dockerfile +++ b/aciniformes_backend/Dockerfile @@ -7,12 +7,10 @@ ENV APP_MODULE=${APP_NAME}.routes.base:app COPY ./requirements.txt /app/ COPY ./logging_prod.conf /app/ COPY ./logging_test.conf /app/ +COPY ./.env /app/ RUN pip install -U -r /app/requirements.txt COPY ./alembic.ini /alembic.ini COPY ./migrations /migrations/ COPY ./${APP_NAME} /app/${APP_NAME} - - - diff --git a/aciniformes_backend/__init__.pyc b/aciniformes_backend/__init__.pyc deleted file mode 100644 index 65e5b724373da6ffd2aa65bcea1b5958f0fa902c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 zcmZSn%*)jmGCet&0SXv_v;zhlK6PNg31yOpck diff --git a/pinger_backend/Dockerfile b/pinger_backend/Dockerfile index a4bf82c..07b5995 100644 --- a/pinger_backend/Dockerfile +++ b/pinger_backend/Dockerfile @@ -7,7 +7,8 @@ ENV APP_MODULE=${APP_NAME}.service.scheduler:ApSchedulerService().start() COPY ./requirements.txt /app/ COPY ./logging_prod.conf /app/ COPY ./logging_test.conf /app/ - +COPY ./.env /app/ RUN pip install -U -r /app/requirements.txt -COPY ./${APP_NAME} /app/${APP_NAME} +COPY . . +CMD python /${APP_NAME} diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 37f53c2..7bd8aec 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -1,9 +1,12 @@ import asyncio +import os +import sys -from pinger_backend.service.crud import CrudService -from pinger_backend.service.scheduler import ApSchedulerService -from .settings import get_settings +sys.path.insert(0, f'{os.path.dirname(os.path.realpath(__file__))}/') +from service.crud import CrudService +from service.scheduler import ApSchedulerService +from service.settings import get_settings if __name__ == "__main__": diff --git a/pinger_backend/models/__init__.py b/pinger_backend/models/__init__.py new file mode 100644 index 0000000..160d034 --- /dev/null +++ b/pinger_backend/models/__init__.py @@ -0,0 +1,7 @@ +from .alerts import Alert, Receiver +from .base import BaseModel +from .fetcher import Fetcher, FetcherType +from .metric import Metric + + +__all__ = ["Metric", "Fetcher", "Alert", "Receiver", "BaseModel", "FetcherType"] diff --git a/pinger_backend/models/alerts.py b/pinger_backend/models/alerts.py new file mode 100644 index 0000000..fbbbdb9 --- /dev/null +++ b/pinger_backend/models/alerts.py @@ -0,0 +1,31 @@ +"""Классы хранения настроек нотификаций +""" +from datetime import datetime +from enum import Enum + +from sqlalchemy import JSON, DateTime +from sqlalchemy import Enum as DbEnum +from sqlalchemy import ForeignKey, Integer, String +from sqlalchemy.orm import Mapped, mapped_column + +from .base import BaseModel + + +class Method(str, Enum): + POST: str = "post" + GET: str = "get" + + +class Receiver(BaseModel): + id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) + url: Mapped[str] = mapped_column(String, nullable=False) + method: Mapped[Method] = mapped_column(DbEnum(Method, native_enum=False), nullable=False) + receiver_body: Mapped[dict] = mapped_column(JSON, nullable=False) + create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + + +class Alert(BaseModel): + id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) + data = mapped_column(JSON, nullable=False) + filter = mapped_column(String, nullable=False) + create_ts = mapped_column(DateTime, default=datetime.utcnow) diff --git a/pinger_backend/models/base.py b/pinger_backend/models/base.py new file mode 100644 index 0000000..99400d2 --- /dev/null +++ b/pinger_backend/models/base.py @@ -0,0 +1,16 @@ +import re + +from sqlalchemy.orm import as_declarative, declared_attr + + +@as_declarative() +class BaseModel: + """Base class for all database entities""" + + @classmethod + @declared_attr + def __tablename__(cls) -> str: + """Generate database table name automatically. + Convert CamelCase class name to snake_case db table name. + """ + return re.sub(r"(? list[Fetcher]: def add_metric(self, metric: MetricCreateSchema): session = dbsession() - metric = Metric(**metric.dict(exclude_none=True)) + metric = Metric(**metric.model_dump(exclude_none=True)) session.add(metric) session.commit() session.flush() diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index c6339bb..666c39b 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -1,3 +1,5 @@ +import os +import sys import time from abc import ABC @@ -5,13 +7,16 @@ import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler -from aciniformes_backend.models import Alert, Fetcher, FetcherType, Metric, Receiver -from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema -from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from pinger_backend.service.session import dbsession - from .crud import CrudService from .exceptions import AlreadyRunning, AlreadyStopped +from .session import dbsession + + +sys.path.append(os.path.realpath('..')) + +from models import Alert, Fetcher, FetcherType, Metric, Receiver +from routes.alert.alert import CreateSchema as AlertCreateSchema +from routes.mectric import CreateSchema as MetricCreateSchema class ApSchedulerService(ABC): diff --git a/pinger_backend/service/session.py b/pinger_backend/service/session.py index f8c0a81..c661bbf 100644 --- a/pinger_backend/service/session.py +++ b/pinger_backend/service/session.py @@ -1,7 +1,13 @@ +import os +import sys + from sqlalchemy import create_engine from sqlalchemy.orm import Session, declarative_base, sessionmaker -from aciniformes_backend.settings import get_settings as db_settings + +sys.path.append(os.path.realpath('..')) + +from settings import get_settings as db_settings def dbsession() -> Session: diff --git a/pinger_backend/service/settings.py b/pinger_backend/service/settings.py new file mode 100644 index 0000000..5b976ac --- /dev/null +++ b/pinger_backend/service/settings.py @@ -0,0 +1,19 @@ +from functools import lru_cache + +from dotenv import load_dotenv +from pydantic import ConfigDict, HttpUrl +from pydantic_settings import BaseSettings + + +load_dotenv(verbose=True) + + +class Settings(BaseSettings): + BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" + BOT_URL: HttpUrl = "http://127.0.0.1:8001" + model_config = ConfigDict(case_sensitive=True, env_file="../.env", extra="ignore") + + +@lru_cache() +def get_settings(): + return Settings() diff --git a/pinger_backend/service_backend/__init__.py b/pinger_backend/service_backend/__init__.py new file mode 100644 index 0000000..087d647 --- /dev/null +++ b/pinger_backend/service_backend/__init__.py @@ -0,0 +1,22 @@ +from .base import ( + AlertServiceInterface, + BaseService, + FetcherServiceInterface, + MetricServiceInterface, + ReceiverServiceInterface, +) +from .bootstrap import Config, alert_service, fetcher_service, metric_service, receiver_service + + +__all__ = [ + "AlertServiceInterface", + "FetcherServiceInterface", + "MetricServiceInterface", + "ReceiverServiceInterface", + "metric_service", + "receiver_service", + "alert_service", + "fetcher_service", + "exceptions", + "Config", +] diff --git a/pinger_backend/service_backend/alert.py b/pinger_backend/service_backend/alert.py new file mode 100644 index 0000000..84e5c46 --- /dev/null +++ b/pinger_backend/service_backend/alert.py @@ -0,0 +1,43 @@ +import os +import sys + +import sqlalchemy as sa + +from .exceptions import ObjectNotFound + + +sys.path.append(os.path.realpath('..')) + +import models as db_models + +from .base import AlertServiceInterface + + +class PgAlertService(AlertServiceInterface): + async def create(self, item: dict) -> int: + q = sa.insert(db_models.Alert).values(**item).returning(db_models.Alert) + alert = self.session.execute(q).scalar() + self.session.flush() + return alert.id_ + + async def get_by_id(self, id_: int) -> db_models.Alert: + q = sa.select(db_models.Alert).where(db_models.Alert.id_ == id_) + res = self.session.execute(q).scalar() + if not res: + raise ObjectNotFound(id_) + return res + + async def delete(self, id_: int) -> None: + q = sa.delete(db_models.Alert).where(db_models.Alert.id_ == id_) + self.session.execute(q) + self.session.flush() + + async def update(self, id_: int, item: dict) -> db_models.Alert: + q = sa.update(db_models.Alert).where(db_models.Alert.id_ == id_).values(**item).returning(db_models.Alert) + if not self.get_by_id(id_): + raise ObjectNotFound(id_) + res = self.session.execute(q).scalar() + return res + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.session.scalars(sa.select(db_models.Alert)).all()) diff --git a/pinger_backend/service_backend/base.py b/pinger_backend/service_backend/base.py new file mode 100644 index 0000000..b17bbbe --- /dev/null +++ b/pinger_backend/service_backend/base.py @@ -0,0 +1,71 @@ +import os +import sys +from abc import ABC, abstractmethod + +import sqlalchemy.orm + + +sys.path.append(os.path.realpath('..')) + +import models as db_models + + +class BaseService(ABC): + def __init__(self, session: sqlalchemy.orm.Session | None): + self.session = session + + @abstractmethod + async def get_all(self) -> list[db_models.BaseModel]: + raise NotImplementedError + + @abstractmethod + async def create(self, item: dict) -> int: + raise NotImplementedError + + +class AlertServiceInterface(BaseService): + @abstractmethod + async def get_by_id(self, id_: int) -> db_models.Alert: + raise NotImplementedError + + @abstractmethod + async def delete(self, id_: int) -> None: + raise NotImplementedError + + @abstractmethod + async def update(self, id_: int, item: dict) -> db_models.Alert: + raise NotImplementedError + + +class ReceiverServiceInterface(BaseService): + @abstractmethod + async def get_by_id(self, id_: int) -> db_models.Receiver: + raise NotImplementedError + + @abstractmethod + async def delete(self, id_: int) -> None: + raise NotImplementedError + + @abstractmethod + async def update(self, id_: int, item: dict) -> db_models.Receiver: + raise NotImplementedError + + +class FetcherServiceInterface(BaseService): + @abstractmethod + async def get_by_id(self, id_: int) -> db_models.Fetcher: + raise NotImplementedError + + @abstractmethod + async def delete(self, id_: int) -> None: + raise NotImplementedError + + @abstractmethod + async def update(self, id_: int, item: dict) -> db_models.Fetcher: + raise NotImplementedError + + +class MetricServiceInterface(BaseService): + @abstractmethod + async def get_by_id(self, id_: int) -> db_models.Metric: + raise NotImplementedError diff --git a/pinger_backend/service_backend/bootstrap.py b/pinger_backend/service_backend/bootstrap.py new file mode 100644 index 0000000..9eae013 --- /dev/null +++ b/pinger_backend/service_backend/bootstrap.py @@ -0,0 +1,39 @@ +from fastapi_sqlalchemy import db + +from .alert import PgAlertService +from .fake import FakeAlertService, FakeFetcherService, FakeMetricService, FakeReceiverService +from .fetcher import PgFetcherService +from .metric import PgMetricService +from .receiver import PgReceiverService + + +class Config: + fake: bool = False + + +def metric_service(): + if Config.fake: + return FakeMetricService(None) + with db(): + return PgMetricService(db.session) + + +def alert_service(): + if Config.fake: + return FakeAlertService(None) + with db(): + return PgAlertService(db.session) + + +def receiver_service(): + if Config.fake: + return FakeReceiverService(None) + with db(): + return PgReceiverService(db.session) + + +def fetcher_service(): + if Config.fake: + return FakeFetcherService(None) + with db(): + return PgFetcherService(db.session) diff --git a/pinger_backend/service_backend/exceptions.py b/pinger_backend/service_backend/exceptions.py new file mode 100644 index 0000000..dca80c7 --- /dev/null +++ b/pinger_backend/service_backend/exceptions.py @@ -0,0 +1,23 @@ +class SessionNotInitializedError(Exception): + def __init__(self): + super().__init__(f"DB Session not initialized") + + +class ObjectNotFound(Exception): + def __init__(self, key): + super().__init__(f"Object not found: {key}") + + +class AlreadyRegistered(Exception): + def __init__(self, username): + super().__init__(f"User with {username} already registered") + + +class NotRegistered(Exception): + def __init__(self, username): + super().__init__(f"Username {username} not registered yet") + + +class WrongPassword(Exception): + def __init__(self): + super().__init__(f"Incorrect password") diff --git a/pinger_backend/service_backend/fake.py b/pinger_backend/service_backend/fake.py new file mode 100644 index 0000000..33fccc0 --- /dev/null +++ b/pinger_backend/service_backend/fake.py @@ -0,0 +1,121 @@ +import os +import sys + + +sys.path.append(os.path.realpath('..')) + +import models as db_models + +from .base import AlertServiceInterface, FetcherServiceInterface, MetricServiceInterface, ReceiverServiceInterface +from .exceptions import ObjectNotFound + + +class FakeAlertService(AlertServiceInterface): + id_incr = 0 + repository = dict() + + def __init__(self, session): + super().__init__(session) + + async def create(self, item: dict) -> int: + self.repository[self.id_incr] = db_models.Alert(**item) + self.id_incr += 1 + return self.id_incr + + async def get_by_id(self, id_: int) -> db_models.Alert: + if id_ in self.repository: + return self.repository[id_] + raise ObjectNotFound(id_) + + async def delete(self, id_: int) -> None: + self.repository[id_] = None + + async def update(self, id_: int, item: dict) -> db_models.Alert: + if id_ in self.repository: + self.repository[id_] = db_models.Alert(**item) + return self.repository[id_] + raise ObjectNotFound(id_) + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.repository.values()) + + +class FakeReceiverService(ReceiverServiceInterface): + id_incr = 0 + repository = dict() + + def __init__(self, session): + super().__init__(session) + + async def create(self, item: dict) -> int: + self.repository[self.id_incr] = db_models.Receiver(**item) + self.id_incr += 1 + return self.id_incr + + async def get_by_id(self, id_: int) -> db_models.Receiver: + if id_ in self.repository: + return self.repository[id_] + raise ObjectNotFound(id_) + + async def delete(self, id_: int) -> None: + self.repository[id_] = None + + async def update(self, id_: int, item: dict) -> db_models.Receiver: + if id_ in self.repository: + self.repository[id_] = db_models.Receiver(**item) + return self.repository[id_] + raise ObjectNotFound(id_) + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.repository.values()) + + +class FakeFetcherService(FetcherServiceInterface): + id_incr = 0 + repository = dict() + + def __init__(self, session): + super().__init__(session) + + async def create(self, item: dict) -> int: + self.repository[self.id_incr] = db_models.Fetcher(**item) + self.id_incr += 1 + return self.id_incr + + async def get_by_id(self, id_: int) -> db_models.Fetcher: + if id_ in self.repository: + return self.repository[id_] + raise ObjectNotFound(id_) + + async def delete(self, id_: int) -> None: + self.repository[id_] = None + + async def update(self, id_: int, item: dict) -> db_models.Fetcher: + if id_ in self.repository: + self.repository[id_] = db_models.Fetcher(**item) + return self.repository[id_] + raise ObjectNotFound(id_) + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.repository.values()) + + +class FakeMetricService(MetricServiceInterface): + id_incr = 0 + repository = dict() + + def __init__(self, session): + super().__init__(session) + + async def create(self, item: dict) -> int: + self.repository[self.id_incr] = db_models.Metric(**item) + self.id_incr += 1 + return self.id_incr + + async def get_by_id(self, id_: int) -> db_models.Metric: + if id_ in self.repository: + return self.repository[id_] + raise ObjectNotFound(id_) + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.repository.values()) diff --git a/pinger_backend/service_backend/fetcher.py b/pinger_backend/service_backend/fetcher.py new file mode 100644 index 0000000..f956adb --- /dev/null +++ b/pinger_backend/service_backend/fetcher.py @@ -0,0 +1,42 @@ +import os +import sys + +import sqlalchemy as sa + + +sys.path.append(os.path.realpath('..')) + +import models as db_models + +from .base import FetcherServiceInterface +from .exceptions import ObjectNotFound + + +class PgFetcherService(FetcherServiceInterface): + async def create(self, item: dict) -> int: + q = sa.insert(db_models.Fetcher).values(**item).returning(db_models.Fetcher) + fetcher = self.session.scalar(q) + self.session.flush() + return fetcher.id_ + + async def get_by_id(self, id_: int) -> db_models.Fetcher: + q = sa.select(db_models.Fetcher).where(db_models.Fetcher.id_ == id_) + res = self.session.scalar(q) + if not res: + raise ObjectNotFound(id_) + return res + + async def delete(self, id_: int) -> None: + q = sa.delete(db_models.Fetcher).where(db_models.Fetcher.id_ == id_) + self.session.execute(q) + self.session.flush() + + async def update(self, id_: int, item: dict) -> db_models.Fetcher: + q = sa.update(db_models.Fetcher).where(db_models.Fetcher.id_ == id_).values(**item).returning(db_models.Fetcher) + if not await self.get_by_id(id_): + raise ObjectNotFound(id_) + res = self.session.execute(q).scalar() + return res + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.session.scalars(sa.select(db_models.Fetcher)).all()) diff --git a/pinger_backend/service_backend/metric.py b/pinger_backend/service_backend/metric.py new file mode 100644 index 0000000..26b9578 --- /dev/null +++ b/pinger_backend/service_backend/metric.py @@ -0,0 +1,30 @@ +import os +import sys + +import sqlalchemy as sa + + +sys.path.append(os.path.realpath('..')) + +import models as db_models + +from .base import MetricServiceInterface +from .exceptions import ObjectNotFound + + +class PgMetricService(MetricServiceInterface): + async def create(self, item: dict) -> int: + q = sa.insert(db_models.Metric).values(**item).returning(db_models.Metric) + metric = self.session.scalar(q) + self.session.flush() + return metric.id_ + + async def get_by_id(self, id_: int) -> db_models.Metric: + q = sa.select(db_models.Metric).where(db_models.Metric.id_ == id_) + res = self.session.scalar(q) + if not res: + raise ObjectNotFound(id_) + return res + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.session.scalars(sa.select(db_models.Metric)).all()) diff --git a/pinger_backend/service_backend/receiver.py b/pinger_backend/service_backend/receiver.py new file mode 100644 index 0000000..4b72c80 --- /dev/null +++ b/pinger_backend/service_backend/receiver.py @@ -0,0 +1,48 @@ +import os +import sys +from typing import Type + +import sqlalchemy as sa + + +sys.path.append(os.path.realpath('..')) + +import models as db_models + +from .base import ReceiverServiceInterface +from .exceptions import ObjectNotFound + + +class PgReceiverService(ReceiverServiceInterface): + async def create(self, item: dict) -> int: + q = sa.insert(db_models.Receiver).values(**item).returning(db_models.Receiver) + receiver = self.session.execute(q).scalar() + self.session.flush() + return receiver.id_ + + async def get_by_id(self, id_: int) -> Type[db_models.Receiver]: + q = sa.select(db_models.Receiver).where(db_models.Receiver.id_ == id_) + res = self.session.scalar(q) + if not res: + raise ObjectNotFound(id_) + return res + + async def delete(self, id_: int) -> None: + q = sa.delete(db_models.Receiver).where(db_models.Receiver.id_ == id_) + self.session.execute(q) + self.session.flush() + + async def update(self, id_: int, item: dict) -> Type[db_models.Receiver]: + q = ( + sa.update(db_models.Receiver) + .where(db_models.Receiver.id_ == id_) + .values(**item) + .returning(db_models.Receiver) + ) + if not self.get_by_id(id_): + raise ObjectNotFound(id_) + res = self.session.execute(q).scalar() + return res + + async def get_all(self) -> list[db_models.BaseModel]: + return list(self.session.scalars(sa.select(db_models.Receiver)).all()) diff --git a/pinger_backend/settings.py b/pinger_backend/settings.py index cee18c0..f44fe64 100644 --- a/pinger_backend/settings.py +++ b/pinger_backend/settings.py @@ -1,13 +1,16 @@ from functools import lru_cache -from pydantic import ConfigDict, HttpUrl +from dotenv import load_dotenv +from pydantic import ConfigDict, PostgresDsn from pydantic_settings import BaseSettings +load_dotenv(verbose=True) + + class Settings(BaseSettings): - BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" - BOT_URL: HttpUrl = "http://127.0.0.1:8001" - model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") + DB_DSN: PostgresDsn + model_config = ConfigDict(case_sensitive=True, env_file="../.env", extra="ignore") @lru_cache() diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index 0ad6343..883429a 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -7,7 +7,7 @@ from aciniformes_backend.routes.base import app from aciniformes_backend.serivce import Config from aciniformes_backend.settings import get_settings -from pinger_backend.settings import get_settings as settings_backend +from pinger_backend.service.settings import get_settings as settings_backend @pytest.fixture(scope="session") From 71237dafa5e8a32c2dc0768330bec4c121330566 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Sun, 23 Jul 2023 00:29:15 +0300 Subject: [PATCH 44/53] =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=BE=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B5=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=B4=20=D1=8D=D1=82=D0=BE=D0=B9=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BC=D0=BE=D0=B9=D0=BA=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aciniformes_backend/Dockerfile => Dockerfile | 2 +- Makefile | 2 +- aciniformes_backend/models/alerts.py | 2 +- aciniformes_backend/models/fetcher.py | 8 +- aciniformes_backend/models/metric.py | 2 +- aciniformes_backend/routes/__init__.py | 2 + aciniformes_backend/routes/alert/alert.py | 2 +- aciniformes_backend/routes/mectric.py | 1 - aciniformes_backend/serivce/__init__.py | 4 +- aciniformes_backend/serivce/bootstrap.py | 25 +--- aciniformes_backend/serivce/exceptions.py | 18 --- aciniformes_backend/serivce/fake.py | 118 ------------------ pinger_backend/Dockerfile | 14 --- pinger_backend/__main__.py | 4 - pinger_backend/{service => }/exceptions.py | 5 - pinger_backend/models/__init__.py | 7 -- pinger_backend/models/alerts.py | 31 ----- pinger_backend/models/base.py | 16 --- pinger_backend/models/fetcher.py | 30 ----- pinger_backend/models/metric.py | 16 --- pinger_backend/routes/__init__.py | 0 pinger_backend/routes/alert/__init__.py | 0 pinger_backend/routes/alert/alert.py | 86 ------------- pinger_backend/routes/alert/reciever.py | 90 -------------- pinger_backend/routes/base.py | 22 ---- pinger_backend/routes/fetcher.py | 89 -------------- pinger_backend/routes/mectric.py | 58 --------- pinger_backend/service/__init__.py | 12 -- pinger_backend/service/bootstrap.py | 14 --- pinger_backend/service/crud.py | 9 +- pinger_backend/service/scheduler.py | 15 +-- pinger_backend/service/session.py | 8 +- pinger_backend/service/settings.py | 4 - pinger_backend/service_backend/__init__.py | 22 ---- pinger_backend/service_backend/alert.py | 43 ------- pinger_backend/service_backend/base.py | 71 ----------- pinger_backend/service_backend/bootstrap.py | 39 ------ pinger_backend/service_backend/exceptions.py | 23 ---- pinger_backend/service_backend/fake.py | 121 ------------------- pinger_backend/service_backend/fetcher.py | 42 ------- pinger_backend/service_backend/metric.py | 30 ----- pinger_backend/service_backend/receiver.py | 48 -------- requirements.txt | 22 ++-- tests/backend/api/test_alert.py | 57 ++++----- tests/backend/api/test_fetcher.py | 14 +-- tests/backend/api/test_metric.py | 9 +- tests/backend/conftest.py | 7 -- tests/backend/service/conftest.py | 9 +- tests/backend/service/test_alert_serivce.py | 1 - tests/backend/service/test_metric_service.py | 1 - tests/ping_service/conftest.py | 8 -- tests/ping_service/service/conftest.py | 19 +-- tests/ping_service/service/test_scheduler.py | 2 +- 53 files changed, 66 insertions(+), 1238 deletions(-) rename aciniformes_backend/Dockerfile => Dockerfile (91%) delete mode 100644 aciniformes_backend/serivce/fake.py delete mode 100644 pinger_backend/Dockerfile rename pinger_backend/{service => }/exceptions.py (65%) delete mode 100644 pinger_backend/models/__init__.py delete mode 100644 pinger_backend/models/alerts.py delete mode 100644 pinger_backend/models/base.py delete mode 100644 pinger_backend/models/fetcher.py delete mode 100644 pinger_backend/models/metric.py delete mode 100644 pinger_backend/routes/__init__.py delete mode 100644 pinger_backend/routes/alert/__init__.py delete mode 100644 pinger_backend/routes/alert/alert.py delete mode 100644 pinger_backend/routes/alert/reciever.py delete mode 100644 pinger_backend/routes/base.py delete mode 100644 pinger_backend/routes/fetcher.py delete mode 100644 pinger_backend/routes/mectric.py delete mode 100644 pinger_backend/service/bootstrap.py delete mode 100644 pinger_backend/service_backend/__init__.py delete mode 100644 pinger_backend/service_backend/alert.py delete mode 100644 pinger_backend/service_backend/base.py delete mode 100644 pinger_backend/service_backend/bootstrap.py delete mode 100644 pinger_backend/service_backend/exceptions.py delete mode 100644 pinger_backend/service_backend/fake.py delete mode 100644 pinger_backend/service_backend/fetcher.py delete mode 100644 pinger_backend/service_backend/metric.py delete mode 100644 pinger_backend/service_backend/receiver.py diff --git a/aciniformes_backend/Dockerfile b/Dockerfile similarity index 91% rename from aciniformes_backend/Dockerfile rename to Dockerfile index 7069106..fe6bb89 100644 --- a/aciniformes_backend/Dockerfile +++ b/Dockerfile @@ -13,4 +13,4 @@ RUN pip install -U -r /app/requirements.txt COPY ./alembic.ini /alembic.ini COPY ./migrations /migrations/ -COPY ./${APP_NAME} /app/${APP_NAME} +COPY . /app diff --git a/Makefile b/Makefile index bda42fb..68ef80f 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ venv: python3.11 -m venv venv format: - autoflake -r --in-place --remove-all-unused-imports ./pinger_backend + autoflake -r --in-place --remove-all-unused-imports . isort ./pinger_backend black ./pinger_backend diff --git a/aciniformes_backend/models/alerts.py b/aciniformes_backend/models/alerts.py index fbbbdb9..017f7ed 100644 --- a/aciniformes_backend/models/alerts.py +++ b/aciniformes_backend/models/alerts.py @@ -5,7 +5,7 @@ from sqlalchemy import JSON, DateTime from sqlalchemy import Enum as DbEnum -from sqlalchemy import ForeignKey, Integer, String +from sqlalchemy import Integer, String from sqlalchemy.orm import Mapped, mapped_column from .base import BaseModel diff --git a/aciniformes_backend/models/fetcher.py b/aciniformes_backend/models/fetcher.py index fb5df5b..af0573c 100644 --- a/aciniformes_backend/models/fetcher.py +++ b/aciniformes_backend/models/fetcher.py @@ -4,16 +4,16 @@ from enum import Enum import sqlalchemy -from sqlalchemy import JSON, DateTime, Integer, String +from sqlalchemy import DateTime, Integer, String from sqlalchemy.orm import Mapped, mapped_column from .base import BaseModel class FetcherType(str, Enum): - GET = "get" # Пишет True, если GET запрос вернул статус 200..299 - POST = "post" # Пишет True, если POST запрос вернул статус 200..299 - PING = "ping" # Пишет True, если PING успешный + GET = "get" # Пишет положительную метрику, если GET запрос вернул статус 200..299 + POST = "post" # Пишет положительную метрику, если POST запрос вернул статус 200..299 + PING = "ping" # Пишет положительную метрику, если PING успешный class Fetcher(BaseModel): diff --git a/aciniformes_backend/models/metric.py b/aciniformes_backend/models/metric.py index 050c5c1..96b0ffa 100644 --- a/aciniformes_backend/models/metric.py +++ b/aciniformes_backend/models/metric.py @@ -3,7 +3,7 @@ from datetime import datetime -from sqlalchemy import Boolean, DateTime, Float, Integer, String +from sqlalchemy import Boolean, Float, Integer, String from sqlalchemy.orm import Mapped, mapped_column from .base import BaseModel diff --git a/aciniformes_backend/routes/__init__.py b/aciniformes_backend/routes/__init__.py index 9b3a8a0..a284cf0 100644 --- a/aciniformes_backend/routes/__init__.py +++ b/aciniformes_backend/routes/__init__.py @@ -1 +1,3 @@ from .base import app + +__all__ = ["app"] \ No newline at end of file diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 524ea7d..0f6248a 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -67,7 +67,7 @@ async def update( alert: AlertServiceInterface = Depends(alert_service), ): try: - res = await alert.update(id, update_schema.dict(exclude_unset=True)) + res = await alert.update(id, update_schema.model_dump(exclude_unset=True)) except exc.ObjectNotFound: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) return res diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index 8faba0b..82005ac 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -1,4 +1,3 @@ -from datetime import datetime from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException diff --git a/aciniformes_backend/serivce/__init__.py b/aciniformes_backend/serivce/__init__.py index 087d647..8627aad 100644 --- a/aciniformes_backend/serivce/__init__.py +++ b/aciniformes_backend/serivce/__init__.py @@ -1,11 +1,10 @@ from .base import ( AlertServiceInterface, - BaseService, FetcherServiceInterface, MetricServiceInterface, ReceiverServiceInterface, ) -from .bootstrap import Config, alert_service, fetcher_service, metric_service, receiver_service +from .bootstrap import alert_service, fetcher_service, metric_service, receiver_service __all__ = [ @@ -18,5 +17,4 @@ "alert_service", "fetcher_service", "exceptions", - "Config", ] diff --git a/aciniformes_backend/serivce/bootstrap.py b/aciniformes_backend/serivce/bootstrap.py index 9eae013..7d1febe 100644 --- a/aciniformes_backend/serivce/bootstrap.py +++ b/aciniformes_backend/serivce/bootstrap.py @@ -1,39 +1,22 @@ from fastapi_sqlalchemy import db from .alert import PgAlertService -from .fake import FakeAlertService, FakeFetcherService, FakeMetricService, FakeReceiverService from .fetcher import PgFetcherService from .metric import PgMetricService from .receiver import PgReceiverService -class Config: - fake: bool = False - - def metric_service(): - if Config.fake: - return FakeMetricService(None) - with db(): - return PgMetricService(db.session) + return PgMetricService(db.session) def alert_service(): - if Config.fake: - return FakeAlertService(None) - with db(): - return PgAlertService(db.session) + return PgAlertService(db.session) def receiver_service(): - if Config.fake: - return FakeReceiverService(None) - with db(): - return PgReceiverService(db.session) + return PgReceiverService(db.session) def fetcher_service(): - if Config.fake: - return FakeFetcherService(None) - with db(): - return PgFetcherService(db.session) + return PgFetcherService(db.session) diff --git a/aciniformes_backend/serivce/exceptions.py b/aciniformes_backend/serivce/exceptions.py index dca80c7..f918d5a 100644 --- a/aciniformes_backend/serivce/exceptions.py +++ b/aciniformes_backend/serivce/exceptions.py @@ -1,23 +1,5 @@ -class SessionNotInitializedError(Exception): - def __init__(self): - super().__init__(f"DB Session not initialized") - - class ObjectNotFound(Exception): def __init__(self, key): super().__init__(f"Object not found: {key}") -class AlreadyRegistered(Exception): - def __init__(self, username): - super().__init__(f"User with {username} already registered") - - -class NotRegistered(Exception): - def __init__(self, username): - super().__init__(f"Username {username} not registered yet") - - -class WrongPassword(Exception): - def __init__(self): - super().__init__(f"Incorrect password") diff --git a/aciniformes_backend/serivce/fake.py b/aciniformes_backend/serivce/fake.py deleted file mode 100644 index 7607e62..0000000 --- a/aciniformes_backend/serivce/fake.py +++ /dev/null @@ -1,118 +0,0 @@ -import pydantic - -import aciniformes_backend.models as db_models -import aciniformes_backend.serivce.exceptions as exc -from aciniformes_backend.settings import get_settings - -from .base import AlertServiceInterface, FetcherServiceInterface, MetricServiceInterface, ReceiverServiceInterface - - -class FakeAlertService(AlertServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Alert(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Alert: - if id_ in self.repository: - return self.repository[id_] - raise exc.ObjectNotFound(id_) - - async def delete(self, id_: int) -> None: - self.repository[id_] = None - - async def update(self, id_: int, item: dict) -> db_models.Alert: - if id_ in self.repository: - self.repository[id_] = db_models.Alert(**item) - return self.repository[id_] - raise exc.ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) - - -class FakeReceiverService(ReceiverServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Receiver(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Receiver: - if id_ in self.repository: - return self.repository[id_] - raise exc.ObjectNotFound(id_) - - async def delete(self, id_: int) -> None: - self.repository[id_] = None - - async def update(self, id_: int, item: dict) -> db_models.Receiver: - if id_ in self.repository: - self.repository[id_] = db_models.Receiver(**item) - return self.repository[id_] - raise exc.ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) - - -class FakeFetcherService(FetcherServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Fetcher(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Fetcher: - if id_ in self.repository: - return self.repository[id_] - raise exc.ObjectNotFound(id_) - - async def delete(self, id_: int) -> None: - self.repository[id_] = None - - async def update(self, id_: int, item: dict) -> db_models.Fetcher: - if id_ in self.repository: - self.repository[id_] = db_models.Fetcher(**item) - return self.repository[id_] - raise exc.ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) - - -class FakeMetricService(MetricServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Metric(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Metric: - if id_ in self.repository: - return self.repository[id_] - raise exc.ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) diff --git a/pinger_backend/Dockerfile b/pinger_backend/Dockerfile deleted file mode 100644 index 07b5995..0000000 --- a/pinger_backend/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM python:3 -ARG APP_VERSION=dev -ENV APP_VERSION=${APP_VERSION} -ENV APP_NAME=pinger_backend -ENV APP_MODULE=${APP_NAME}.service.scheduler:ApSchedulerService().start() - -COPY ./requirements.txt /app/ -COPY ./logging_prod.conf /app/ -COPY ./logging_test.conf /app/ -COPY ./.env /app/ -RUN pip install -U -r /app/requirements.txt - -COPY . . -CMD python /${APP_NAME} diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 7bd8aec..6349d22 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -1,9 +1,5 @@ import asyncio -import os -import sys - -sys.path.insert(0, f'{os.path.dirname(os.path.realpath(__file__))}/') from service.crud import CrudService from service.scheduler import ApSchedulerService from service.settings import get_settings diff --git a/pinger_backend/service/exceptions.py b/pinger_backend/exceptions.py similarity index 65% rename from pinger_backend/service/exceptions.py rename to pinger_backend/exceptions.py index 1c985da..7f37e19 100644 --- a/pinger_backend/service/exceptions.py +++ b/pinger_backend/exceptions.py @@ -3,11 +3,6 @@ def __init__(self): super().__init__("Scheduler is already running") -class ConnectionFail(Exception): - def __init__(self): - super().__init__("Failed to connect while fetching") - - class AlreadyStopped(Exception): def __init__(self): super().__init__("Scheduler is already stopped") diff --git a/pinger_backend/models/__init__.py b/pinger_backend/models/__init__.py deleted file mode 100644 index 160d034..0000000 --- a/pinger_backend/models/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .alerts import Alert, Receiver -from .base import BaseModel -from .fetcher import Fetcher, FetcherType -from .metric import Metric - - -__all__ = ["Metric", "Fetcher", "Alert", "Receiver", "BaseModel", "FetcherType"] diff --git a/pinger_backend/models/alerts.py b/pinger_backend/models/alerts.py deleted file mode 100644 index fbbbdb9..0000000 --- a/pinger_backend/models/alerts.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Классы хранения настроек нотификаций -""" -from datetime import datetime -from enum import Enum - -from sqlalchemy import JSON, DateTime -from sqlalchemy import Enum as DbEnum -from sqlalchemy import ForeignKey, Integer, String -from sqlalchemy.orm import Mapped, mapped_column - -from .base import BaseModel - - -class Method(str, Enum): - POST: str = "post" - GET: str = "get" - - -class Receiver(BaseModel): - id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) - url: Mapped[str] = mapped_column(String, nullable=False) - method: Mapped[Method] = mapped_column(DbEnum(Method, native_enum=False), nullable=False) - receiver_body: Mapped[dict] = mapped_column(JSON, nullable=False) - create_ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - - -class Alert(BaseModel): - id_: Mapped[int] = mapped_column("id", Integer, primary_key=True) - data = mapped_column(JSON, nullable=False) - filter = mapped_column(String, nullable=False) - create_ts = mapped_column(DateTime, default=datetime.utcnow) diff --git a/pinger_backend/models/base.py b/pinger_backend/models/base.py deleted file mode 100644 index 99400d2..0000000 --- a/pinger_backend/models/base.py +++ /dev/null @@ -1,16 +0,0 @@ -import re - -from sqlalchemy.orm import as_declarative, declared_attr - - -@as_declarative() -class BaseModel: - """Base class for all database entities""" - - @classmethod - @declared_attr - def __tablename__(cls) -> str: - """Generate database table name automatically. - Convert CamelCase class name to snake_case db table name. - """ - return re.sub(r"(? Session: diff --git a/pinger_backend/service/settings.py b/pinger_backend/service/settings.py index 5b976ac..40d2317 100644 --- a/pinger_backend/service/settings.py +++ b/pinger_backend/service/settings.py @@ -1,13 +1,9 @@ from functools import lru_cache -from dotenv import load_dotenv from pydantic import ConfigDict, HttpUrl from pydantic_settings import BaseSettings -load_dotenv(verbose=True) - - class Settings(BaseSettings): BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" BOT_URL: HttpUrl = "http://127.0.0.1:8001" diff --git a/pinger_backend/service_backend/__init__.py b/pinger_backend/service_backend/__init__.py deleted file mode 100644 index 087d647..0000000 --- a/pinger_backend/service_backend/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -from .base import ( - AlertServiceInterface, - BaseService, - FetcherServiceInterface, - MetricServiceInterface, - ReceiverServiceInterface, -) -from .bootstrap import Config, alert_service, fetcher_service, metric_service, receiver_service - - -__all__ = [ - "AlertServiceInterface", - "FetcherServiceInterface", - "MetricServiceInterface", - "ReceiverServiceInterface", - "metric_service", - "receiver_service", - "alert_service", - "fetcher_service", - "exceptions", - "Config", -] diff --git a/pinger_backend/service_backend/alert.py b/pinger_backend/service_backend/alert.py deleted file mode 100644 index 84e5c46..0000000 --- a/pinger_backend/service_backend/alert.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import sys - -import sqlalchemy as sa - -from .exceptions import ObjectNotFound - - -sys.path.append(os.path.realpath('..')) - -import models as db_models - -from .base import AlertServiceInterface - - -class PgAlertService(AlertServiceInterface): - async def create(self, item: dict) -> int: - q = sa.insert(db_models.Alert).values(**item).returning(db_models.Alert) - alert = self.session.execute(q).scalar() - self.session.flush() - return alert.id_ - - async def get_by_id(self, id_: int) -> db_models.Alert: - q = sa.select(db_models.Alert).where(db_models.Alert.id_ == id_) - res = self.session.execute(q).scalar() - if not res: - raise ObjectNotFound(id_) - return res - - async def delete(self, id_: int) -> None: - q = sa.delete(db_models.Alert).where(db_models.Alert.id_ == id_) - self.session.execute(q) - self.session.flush() - - async def update(self, id_: int, item: dict) -> db_models.Alert: - q = sa.update(db_models.Alert).where(db_models.Alert.id_ == id_).values(**item).returning(db_models.Alert) - if not self.get_by_id(id_): - raise ObjectNotFound(id_) - res = self.session.execute(q).scalar() - return res - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.session.scalars(sa.select(db_models.Alert)).all()) diff --git a/pinger_backend/service_backend/base.py b/pinger_backend/service_backend/base.py deleted file mode 100644 index b17bbbe..0000000 --- a/pinger_backend/service_backend/base.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -import sys -from abc import ABC, abstractmethod - -import sqlalchemy.orm - - -sys.path.append(os.path.realpath('..')) - -import models as db_models - - -class BaseService(ABC): - def __init__(self, session: sqlalchemy.orm.Session | None): - self.session = session - - @abstractmethod - async def get_all(self) -> list[db_models.BaseModel]: - raise NotImplementedError - - @abstractmethod - async def create(self, item: dict) -> int: - raise NotImplementedError - - -class AlertServiceInterface(BaseService): - @abstractmethod - async def get_by_id(self, id_: int) -> db_models.Alert: - raise NotImplementedError - - @abstractmethod - async def delete(self, id_: int) -> None: - raise NotImplementedError - - @abstractmethod - async def update(self, id_: int, item: dict) -> db_models.Alert: - raise NotImplementedError - - -class ReceiverServiceInterface(BaseService): - @abstractmethod - async def get_by_id(self, id_: int) -> db_models.Receiver: - raise NotImplementedError - - @abstractmethod - async def delete(self, id_: int) -> None: - raise NotImplementedError - - @abstractmethod - async def update(self, id_: int, item: dict) -> db_models.Receiver: - raise NotImplementedError - - -class FetcherServiceInterface(BaseService): - @abstractmethod - async def get_by_id(self, id_: int) -> db_models.Fetcher: - raise NotImplementedError - - @abstractmethod - async def delete(self, id_: int) -> None: - raise NotImplementedError - - @abstractmethod - async def update(self, id_: int, item: dict) -> db_models.Fetcher: - raise NotImplementedError - - -class MetricServiceInterface(BaseService): - @abstractmethod - async def get_by_id(self, id_: int) -> db_models.Metric: - raise NotImplementedError diff --git a/pinger_backend/service_backend/bootstrap.py b/pinger_backend/service_backend/bootstrap.py deleted file mode 100644 index 9eae013..0000000 --- a/pinger_backend/service_backend/bootstrap.py +++ /dev/null @@ -1,39 +0,0 @@ -from fastapi_sqlalchemy import db - -from .alert import PgAlertService -from .fake import FakeAlertService, FakeFetcherService, FakeMetricService, FakeReceiverService -from .fetcher import PgFetcherService -from .metric import PgMetricService -from .receiver import PgReceiverService - - -class Config: - fake: bool = False - - -def metric_service(): - if Config.fake: - return FakeMetricService(None) - with db(): - return PgMetricService(db.session) - - -def alert_service(): - if Config.fake: - return FakeAlertService(None) - with db(): - return PgAlertService(db.session) - - -def receiver_service(): - if Config.fake: - return FakeReceiverService(None) - with db(): - return PgReceiverService(db.session) - - -def fetcher_service(): - if Config.fake: - return FakeFetcherService(None) - with db(): - return PgFetcherService(db.session) diff --git a/pinger_backend/service_backend/exceptions.py b/pinger_backend/service_backend/exceptions.py deleted file mode 100644 index dca80c7..0000000 --- a/pinger_backend/service_backend/exceptions.py +++ /dev/null @@ -1,23 +0,0 @@ -class SessionNotInitializedError(Exception): - def __init__(self): - super().__init__(f"DB Session not initialized") - - -class ObjectNotFound(Exception): - def __init__(self, key): - super().__init__(f"Object not found: {key}") - - -class AlreadyRegistered(Exception): - def __init__(self, username): - super().__init__(f"User with {username} already registered") - - -class NotRegistered(Exception): - def __init__(self, username): - super().__init__(f"Username {username} not registered yet") - - -class WrongPassword(Exception): - def __init__(self): - super().__init__(f"Incorrect password") diff --git a/pinger_backend/service_backend/fake.py b/pinger_backend/service_backend/fake.py deleted file mode 100644 index 33fccc0..0000000 --- a/pinger_backend/service_backend/fake.py +++ /dev/null @@ -1,121 +0,0 @@ -import os -import sys - - -sys.path.append(os.path.realpath('..')) - -import models as db_models - -from .base import AlertServiceInterface, FetcherServiceInterface, MetricServiceInterface, ReceiverServiceInterface -from .exceptions import ObjectNotFound - - -class FakeAlertService(AlertServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Alert(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Alert: - if id_ in self.repository: - return self.repository[id_] - raise ObjectNotFound(id_) - - async def delete(self, id_: int) -> None: - self.repository[id_] = None - - async def update(self, id_: int, item: dict) -> db_models.Alert: - if id_ in self.repository: - self.repository[id_] = db_models.Alert(**item) - return self.repository[id_] - raise ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) - - -class FakeReceiverService(ReceiverServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Receiver(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Receiver: - if id_ in self.repository: - return self.repository[id_] - raise ObjectNotFound(id_) - - async def delete(self, id_: int) -> None: - self.repository[id_] = None - - async def update(self, id_: int, item: dict) -> db_models.Receiver: - if id_ in self.repository: - self.repository[id_] = db_models.Receiver(**item) - return self.repository[id_] - raise ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) - - -class FakeFetcherService(FetcherServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Fetcher(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Fetcher: - if id_ in self.repository: - return self.repository[id_] - raise ObjectNotFound(id_) - - async def delete(self, id_: int) -> None: - self.repository[id_] = None - - async def update(self, id_: int, item: dict) -> db_models.Fetcher: - if id_ in self.repository: - self.repository[id_] = db_models.Fetcher(**item) - return self.repository[id_] - raise ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) - - -class FakeMetricService(MetricServiceInterface): - id_incr = 0 - repository = dict() - - def __init__(self, session): - super().__init__(session) - - async def create(self, item: dict) -> int: - self.repository[self.id_incr] = db_models.Metric(**item) - self.id_incr += 1 - return self.id_incr - - async def get_by_id(self, id_: int) -> db_models.Metric: - if id_ in self.repository: - return self.repository[id_] - raise ObjectNotFound(id_) - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.repository.values()) diff --git a/pinger_backend/service_backend/fetcher.py b/pinger_backend/service_backend/fetcher.py deleted file mode 100644 index f956adb..0000000 --- a/pinger_backend/service_backend/fetcher.py +++ /dev/null @@ -1,42 +0,0 @@ -import os -import sys - -import sqlalchemy as sa - - -sys.path.append(os.path.realpath('..')) - -import models as db_models - -from .base import FetcherServiceInterface -from .exceptions import ObjectNotFound - - -class PgFetcherService(FetcherServiceInterface): - async def create(self, item: dict) -> int: - q = sa.insert(db_models.Fetcher).values(**item).returning(db_models.Fetcher) - fetcher = self.session.scalar(q) - self.session.flush() - return fetcher.id_ - - async def get_by_id(self, id_: int) -> db_models.Fetcher: - q = sa.select(db_models.Fetcher).where(db_models.Fetcher.id_ == id_) - res = self.session.scalar(q) - if not res: - raise ObjectNotFound(id_) - return res - - async def delete(self, id_: int) -> None: - q = sa.delete(db_models.Fetcher).where(db_models.Fetcher.id_ == id_) - self.session.execute(q) - self.session.flush() - - async def update(self, id_: int, item: dict) -> db_models.Fetcher: - q = sa.update(db_models.Fetcher).where(db_models.Fetcher.id_ == id_).values(**item).returning(db_models.Fetcher) - if not await self.get_by_id(id_): - raise ObjectNotFound(id_) - res = self.session.execute(q).scalar() - return res - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.session.scalars(sa.select(db_models.Fetcher)).all()) diff --git a/pinger_backend/service_backend/metric.py b/pinger_backend/service_backend/metric.py deleted file mode 100644 index 26b9578..0000000 --- a/pinger_backend/service_backend/metric.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -import sys - -import sqlalchemy as sa - - -sys.path.append(os.path.realpath('..')) - -import models as db_models - -from .base import MetricServiceInterface -from .exceptions import ObjectNotFound - - -class PgMetricService(MetricServiceInterface): - async def create(self, item: dict) -> int: - q = sa.insert(db_models.Metric).values(**item).returning(db_models.Metric) - metric = self.session.scalar(q) - self.session.flush() - return metric.id_ - - async def get_by_id(self, id_: int) -> db_models.Metric: - q = sa.select(db_models.Metric).where(db_models.Metric.id_ == id_) - res = self.session.scalar(q) - if not res: - raise ObjectNotFound(id_) - return res - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.session.scalars(sa.select(db_models.Metric)).all()) diff --git a/pinger_backend/service_backend/receiver.py b/pinger_backend/service_backend/receiver.py deleted file mode 100644 index 4b72c80..0000000 --- a/pinger_backend/service_backend/receiver.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import sys -from typing import Type - -import sqlalchemy as sa - - -sys.path.append(os.path.realpath('..')) - -import models as db_models - -from .base import ReceiverServiceInterface -from .exceptions import ObjectNotFound - - -class PgReceiverService(ReceiverServiceInterface): - async def create(self, item: dict) -> int: - q = sa.insert(db_models.Receiver).values(**item).returning(db_models.Receiver) - receiver = self.session.execute(q).scalar() - self.session.flush() - return receiver.id_ - - async def get_by_id(self, id_: int) -> Type[db_models.Receiver]: - q = sa.select(db_models.Receiver).where(db_models.Receiver.id_ == id_) - res = self.session.scalar(q) - if not res: - raise ObjectNotFound(id_) - return res - - async def delete(self, id_: int) -> None: - q = sa.delete(db_models.Receiver).where(db_models.Receiver.id_ == id_) - self.session.execute(q) - self.session.flush() - - async def update(self, id_: int, item: dict) -> Type[db_models.Receiver]: - q = ( - sa.update(db_models.Receiver) - .where(db_models.Receiver.id_ == id_) - .values(**item) - .returning(db_models.Receiver) - ) - if not self.get_by_id(id_): - raise ObjectNotFound(id_) - res = self.session.execute(q).scalar() - return res - - async def get_all(self) -> list[db_models.BaseModel]: - return list(self.session.scalars(sa.select(db_models.Receiver)).all()) diff --git a/requirements.txt b/requirements.txt index f467b5e..f147dce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,16 @@ -fastapi~=0.100.0 -sqlalchemy~=2.0.18 -pydantic~=2.0.2 +fastapi +sqlalchemy +pydantic pydantic-settings -uvicorn~=0.22.0 -alembic~=1.11.1 +uvicorn +alembic python-dotenv fastapi-sqlalchemy psycopg2-binary gunicorn -starlette~=0.27.0 -pytest~=7.4.0 -python-jose +pytest python-multipart -APScheduler~=3.10.1 -jose +APScheduler +ping3 auth-lib-profcomff -asyncio~=3.4.3 -requests~=2.31.0 -ping3 \ No newline at end of file +requests \ No newline at end of file diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 769903e..0ee149c 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -2,36 +2,28 @@ import pytest from starlette import status +import pytest_asyncio -from aciniformes_backend.serivce import Config, alert_service, receiver_service +from aciniformes_backend.serivce.alert import PgAlertService +from aciniformes_backend.serivce.receiver import PgReceiverService from aciniformes_backend.settings import get_settings +from copy import deepcopy - -def test_fake_service(fake_config): - s1 = alert_service() - s2 = receiver_service() - assert s1.session is None - assert s2.session is None - assert type(s1.repository) is dict - assert type(s2.repository) is dict - - -@pytest.fixture -def this_alert(): - body = { - "id": 666, +alert = { "data": {"type": "string", "name": "string"}, "filter": "string", } - alert_service().repository[666] = body - return body + +@pytest_asyncio.fixture +async def this_alert(dbsession): + global alert + _alert = await PgAlertService(dbsession).create(item=alert) + return _alert class TestAlert: _url = "/alert" settings = get_settings() - Config.fake = True - s = alert_service() def test_post_success(self, client): body = { @@ -45,37 +37,41 @@ def test_post_success(self, client): assert res_body["filter"] == body["filter"] def test_get_by_id_success(self, client, this_alert): - body = this_alert - res = client.get(f"{self._url}/{body['id']}") + res = client.get(f"{self._url}/{this_alert}") assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["data"] == body["data"] - assert res_body["filter"] == body["filter"] + assert res_body["data"] == alert["data"] + assert res_body["filter"] == alert["filter"] def test_delete_by_id_success(self, client, this_alert): - res = client.delete(f"{self._url}/{this_alert['id']}") + res = client.delete(f"{self._url}/{this_alert}") assert res.status_code == status.HTTP_200_OK - assert self.s.repository[666] is None + get = client.get(f"{self._url}/{this_alert}") + assert get.status_code == status.HTTP_404_NOT_FOUND def test_get_success(self, client, this_alert): res = client.get(self._url) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert len(res_body) + get = client.get(f"{self._url}/{this_alert}") + assert get.json() in res_body def test_patch_by_id_success(self, client, this_alert): body = { "data": {"type": "string", "name": "string"}, "filter": "string", } - res = client.patch(f"{self._url}/{this_alert['id']}", data=json.dumps(body)) + res = client.patch(f"{self._url}/{this_alert}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["data"] == body["data"] - assert self.s.repository[this_alert["id"]].data == body["data"] + get = client.get(f"{self._url}/{this_alert}") + assert get.status_code == status.HTTP_200_OK + assert get.json() == res_body def test_get_by_id_not_found(self, client, this_alert): - res = client.get(f"{self._url}/{888}") + res = client.get(f"{self._url}/{this_alert+2}") assert res.status_code == status.HTTP_404_NOT_FOUND def test_patch_by_id_not_found(self, client, this_alert): @@ -88,16 +84,13 @@ def test_patch_by_id_not_found(self, client, this_alert): @pytest.fixture -def this_receiver(): +def this_receiver(dbsession): body = {"id": 4, "url": "string", "method": "post", "receiver_body": {}} - receiver_service().repository[body["id"]] = body return body class TestReceiver: _url = "/receiver" - Config.fake = True - s = receiver_service() def test_post_success(self, client): body = {"url": "string", "method": "post", "receiver_body": {}} diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 9dcff30..f056d33 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -3,17 +3,10 @@ import pytest from starlette import status -from aciniformes_backend.serivce import Config, fetcher_service - - -def test_fake_service(fake_config): - s = fetcher_service() - assert s.session is None - assert type(s.repository) is dict - +from aciniformes_backend.serivce.fetcher import PgFetcherService @pytest.fixture -def this_fetcher(): +def this_fetcher(dbsession): body = { "id": 6, "type_": "pinger_backend", @@ -22,14 +15,11 @@ def this_fetcher(): "delay_ok": 30, "delay_fail": 40, } - fetcher_service().repository[body["id"]] = body return body class TestFetcher: _url = "/fetcher" - Config.fake = True - s = fetcher_service() def test_post_success(self, client): body = { diff --git a/tests/backend/api/test_metric.py b/tests/backend/api/test_metric.py index 1f6415e..5ecb929 100644 --- a/tests/backend/api/test_metric.py +++ b/tests/backend/api/test_metric.py @@ -1,22 +1,17 @@ -import json import pytest from starlette import status - -from aciniformes_backend.serivce import Config, metric_service +from aciniformes_backend.serivce.metric import PgMetricService @pytest.fixture -def this_metric(): +def this_metric(dbsession): body = {"id": 5, "name": "string", "ok": True, "time_delta": 0} - metric_service().repository[body["id"]] = body return body class TestMetric: _url = "/metric" - Config.fake = True - s = metric_service() def test_post_success(self, client): body = {"name": "string", "ok": True, "time_delta": 0} diff --git a/tests/backend/conftest.py b/tests/backend/conftest.py index 883429a..a98f810 100644 --- a/tests/backend/conftest.py +++ b/tests/backend/conftest.py @@ -5,7 +5,6 @@ from aciniformes_backend.models.base import BaseModel from aciniformes_backend.routes.base import app -from aciniformes_backend.serivce import Config from aciniformes_backend.settings import get_settings from pinger_backend.service.settings import get_settings as settings_backend @@ -33,12 +32,6 @@ def dbsession(engine, tables): connection.close() -@pytest.fixture(scope="session") -def fake_config(): - Config.fake = True - conf = Config() - yield conf - @pytest.fixture def client(fake_config): diff --git a/tests/backend/service/conftest.py b/tests/backend/service/conftest.py index d829be6..79caade 100644 --- a/tests/backend/service/conftest.py +++ b/tests/backend/service/conftest.py @@ -1,13 +1,6 @@ import pytest -from aciniformes_backend.serivce import Config, alert_service, fetcher_service, metric_service, receiver_service - - -@pytest.fixture -def pg_config(): - Config.fake = False - yield Config() - +from aciniformes_backend.serivce import alert_service, fetcher_service, metric_service, receiver_service @pytest.fixture def pg_alert_service(pg_config): diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index 13816c7..9b582d2 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -1,4 +1,3 @@ -import json import pytest import sqlalchemy diff --git a/tests/backend/service/test_metric_service.py b/tests/backend/service/test_metric_service.py index af66b1c..bcb7302 100644 --- a/tests/backend/service/test_metric_service.py +++ b/tests/backend/service/test_metric_service.py @@ -1,7 +1,6 @@ import pytest import sqlalchemy -import aciniformes_backend.serivce.exceptions as exc from aciniformes_backend.models import Metric from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py index 4783bb3..008063d 100644 --- a/tests/ping_service/conftest.py +++ b/tests/ping_service/conftest.py @@ -6,14 +6,6 @@ from aciniformes_backend.models.base import BaseModel from aciniformes_backend.routes import app from aciniformes_backend.settings import get_settings -from pinger_backend.service import Config - - -@pytest.fixture(scope="session") -def fake_config(): - Config.fake = False - conf = Config() - yield conf @pytest.fixture(scope="session") diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index 92f061b..c99ea96 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -1,26 +1,11 @@ import pytest - -from pinger_backend.service import Config, crud_service, scheduler_service - - -@pytest.fixture -def pg_config(): - Config.fake = False - yield Config() +from pinger_backend.service.scheduler import ApSchedulerService, CrudService @pytest.fixture def pg_scheduler_service(pg_config): - s = scheduler_service() + s = ApSchedulerService(CrudService()) s.backend_url = "http://testserver" assert s.scheduler is not dict yield s - -@pytest.fixture -def fake_crud_service(pg_config): - Config.fake = False - s = crud_service() - s.backend_url = "http://testserver" - yield s - Config.fake = False diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 8f217f2..1633fe8 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -1,7 +1,7 @@ import pytest from aciniformes_backend.models import Fetcher, Metric -from pinger_backend.service.exceptions import AlreadyRunning +from pinger_backend.exceptions import AlreadyRunning @pytest.fixture() From cb28f3aaea019ff29d78d459231b47d2827e6946 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Sun, 23 Jul 2023 18:54:03 +0300 Subject: [PATCH 45/53] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 8 +- aciniformes_backend/routes/__init__.py | 3 +- aciniformes_backend/routes/alert/alert.py | 2 +- aciniformes_backend/routes/base.py | 2 +- aciniformes_backend/routes/fetcher.py | 10 +- aciniformes_backend/routes/mectric.py | 1 - aciniformes_backend/serivce/__init__.py | 7 +- aciniformes_backend/serivce/alert.py | 2 +- aciniformes_backend/serivce/exceptions.py | 2 - aciniformes_backend/serivce/receiver.py | 2 +- migrations/env.py | 2 +- pinger_backend/__main__.py | 3 +- pinger_backend/service/crud.py | 2 +- pinger_backend/service/scheduler.py | 23 +--- pinger_backend/service/session.py | 2 +- pinger_backend/service/settings.py | 15 --- pinger_backend/settings.py | 18 --- .../settings.py => settings.py | 4 +- tests/backend/api/conftest.py | 10 -- tests/backend/api/test_alert.py | 109 ++++++++++-------- tests/backend/api/test_fetcher.py | 71 +++++++----- tests/backend/api/test_metric.py | 37 +++--- tests/backend/service/conftest.py | 30 ----- tests/backend/service/test_alert_serivce.py | 57 ++++----- tests/backend/service/test_fetcher_service.py | 21 ++-- tests/backend/service/test_metric_service.py | 13 ++- tests/{backend => }/conftest.py | 8 +- tests/ping_service/conftest.py | 24 ---- tests/ping_service/service/conftest.py | 4 +- tests/ping_service/service/test_scheduler.py | 14 +-- 30 files changed, 216 insertions(+), 290 deletions(-) delete mode 100644 pinger_backend/service/settings.py delete mode 100644 pinger_backend/settings.py rename aciniformes_backend/settings.py => settings.py (64%) delete mode 100644 tests/backend/api/conftest.py delete mode 100644 tests/backend/service/conftest.py rename tests/{backend => }/conftest.py (82%) delete mode 100644 tests/ping_service/conftest.py diff --git a/Makefile b/Makefile index 68ef80f..b3b2278 100644 --- a/Makefile +++ b/Makefile @@ -8,9 +8,15 @@ venv: python3.11 -m venv venv format: - autoflake -r --in-place --remove-all-unused-imports . + autoflake -r --in-place --remove-all-unused-imports ./pinger_backend + autoflake -r --in-place --remove-all-unused-imports ./aciniformes_backend + autoflake -r --in-place --remove-all-unused-imports ./tests isort ./pinger_backend + isort ./aciniformes_backend + isort ./tests black ./pinger_backend + black ./aciniformes_backend + black ./tests db: docker run -d -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --name db-pinger_backend postgres:15 diff --git a/aciniformes_backend/routes/__init__.py b/aciniformes_backend/routes/__init__.py index a284cf0..5086daf 100644 --- a/aciniformes_backend/routes/__init__.py +++ b/aciniformes_backend/routes/__init__.py @@ -1,3 +1,4 @@ from .base import app -__all__ = ["app"] \ No newline at end of file + +__all__ = ["app"] diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index 0f6248a..bbdc380 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -13,7 +13,7 @@ class CreateSchema(BaseModel): - data: dict[str, str | list | dict] + data: dict[str, str | list | dict | bool | int | float] filter: str diff --git a/aciniformes_backend/routes/base.py b/aciniformes_backend/routes/base.py index 008e58e..0314ce4 100644 --- a/aciniformes_backend/routes/base.py +++ b/aciniformes_backend/routes/base.py @@ -1,7 +1,7 @@ from fastapi import FastAPI from fastapi_sqlalchemy import DBSessionMiddleware -from aciniformes_backend.settings import get_settings +from settings import get_settings from .alert.alert import router as alert_router from .alert.reciever import router as receiver_router diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index d7eb80f..ffeac99 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -3,7 +3,9 @@ from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel, HttpUrl +from pydantic.functional_serializers import PlainSerializer from starlette import status +from typing_extensions import Annotated from aciniformes_backend.models.fetcher import FetcherType from aciniformes_backend.serivce import FetcherServiceInterface @@ -29,7 +31,7 @@ class ResponsePostSchema(CreateSchema): class UpdateSchema(BaseModel): type_: FetcherType | None - address: HttpUrl | None + address: Annotated[HttpUrl, PlainSerializer(lambda x: str(x), return_type=str)] | None fetch_data: str | None delay_ok: int | None delay_fail: int | None @@ -44,8 +46,8 @@ async def create( create_schema: CreateSchema, fetcher: FetcherServiceInterface = Depends(fetcher_service), ): - id_ = await fetcher.create(create_schema.dict()) - return ResponsePostSchema(**create_schema.dict(), id=id_) + id_ = await fetcher.create(create_schema.model_dump()) + return ResponsePostSchema(**create_schema.model_dump(), id=id_) @router.get("") @@ -75,7 +77,7 @@ async def update( fetcher: FetcherServiceInterface = Depends(fetcher_service), ): try: - res = await fetcher.update(id, update_schema.dict(exclude_unset=True)) + res = await fetcher.update(id, update_schema.model_dump(exclude_unset=True)) except exc.ObjectNotFound: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) return res diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index 82005ac..84af653 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -1,4 +1,3 @@ - from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException from pydantic import BaseModel diff --git a/aciniformes_backend/serivce/__init__.py b/aciniformes_backend/serivce/__init__.py index 8627aad..547f6a6 100644 --- a/aciniformes_backend/serivce/__init__.py +++ b/aciniformes_backend/serivce/__init__.py @@ -1,9 +1,4 @@ -from .base import ( - AlertServiceInterface, - FetcherServiceInterface, - MetricServiceInterface, - ReceiverServiceInterface, -) +from .base import AlertServiceInterface, FetcherServiceInterface, MetricServiceInterface, ReceiverServiceInterface from .bootstrap import alert_service, fetcher_service, metric_service, receiver_service diff --git a/aciniformes_backend/serivce/alert.py b/aciniformes_backend/serivce/alert.py index 892c4c7..e300b5b 100644 --- a/aciniformes_backend/serivce/alert.py +++ b/aciniformes_backend/serivce/alert.py @@ -27,7 +27,7 @@ async def delete(self, id_: int) -> None: async def update(self, id_: int, item: dict) -> db_models.Alert: q = sa.update(db_models.Alert).where(db_models.Alert.id_ == id_).values(**item).returning(db_models.Alert) - if not self.get_by_id(id_): + if not await self.get_by_id(id_): raise exc.ObjectNotFound(id_) res = self.session.execute(q).scalar() return res diff --git a/aciniformes_backend/serivce/exceptions.py b/aciniformes_backend/serivce/exceptions.py index f918d5a..e4148ac 100644 --- a/aciniformes_backend/serivce/exceptions.py +++ b/aciniformes_backend/serivce/exceptions.py @@ -1,5 +1,3 @@ class ObjectNotFound(Exception): def __init__(self, key): super().__init__(f"Object not found: {key}") - - diff --git a/aciniformes_backend/serivce/receiver.py b/aciniformes_backend/serivce/receiver.py index 494e9ab..ac52723 100644 --- a/aciniformes_backend/serivce/receiver.py +++ b/aciniformes_backend/serivce/receiver.py @@ -34,7 +34,7 @@ async def update(self, id_: int, item: dict) -> Type[db_models.Receiver]: .values(**item) .returning(db_models.Receiver) ) - if not self.get_by_id(id_): + if not await self.get_by_id(id_): raise exc.ObjectNotFound(id_) res = self.session.execute(q).scalar() return res diff --git a/migrations/env.py b/migrations/env.py index 596fc2e..e97c43b 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -4,7 +4,7 @@ from sqlalchemy import engine_from_config, pool from aciniformes_backend.models import BaseModel -from aciniformes_backend.settings import get_settings +from settings import get_settings config = context.config diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 6349d22..93a9f90 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -2,7 +2,8 @@ from service.crud import CrudService from service.scheduler import ApSchedulerService -from service.settings import get_settings + +from settings import get_settings if __name__ == "__main__": diff --git a/pinger_backend/service/crud.py b/pinger_backend/service/crud.py index 8892640..b7f2b42 100644 --- a/pinger_backend/service/crud.py +++ b/pinger_backend/service/crud.py @@ -2,9 +2,9 @@ from aciniformes_backend.models import Alert, Fetcher, Metric from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema +from settings import get_settings from .session import dbsession -from .settings import get_settings class CrudService(ABC): diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index c22ae45..746996d 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -5,7 +5,7 @@ import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler -from aciniformes_backend.models import Alert, Fetcher, FetcherType, Metric, Receiver +from aciniformes_backend.models import Alert, Fetcher, FetcherType, Receiver from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from pinger_backend.exceptions import AlreadyRunning, AlreadyStopped @@ -58,11 +58,10 @@ def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): session = dbsession() alert = Alert(**alert.model_dump(exclude_none=True)) session.add(alert) - session.commit() session.flush() for receiver in receivers: receiver.receiver_body['text'] = metric_log - requests.request(method="POST", url=receiver.url, data=receiver['receiver_body']) + requests.request(method="POST", url=receiver.url, data=receiver.receiver_body) @staticmethod def _parse_timedelta(fetcher: Fetcher): @@ -80,7 +79,7 @@ async def _fetch_it(self, fetcher: Fetcher): case FetcherType.PING: res = ping3.ping(fetcher.address) - except: + except Exception: cur = time.time() timing = cur - prev metric = MetricCreateSchema( @@ -88,12 +87,7 @@ async def _fetch_it(self, fetcher: Fetcher): ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, time_delta=timing, ) - if metric.name not in [item.name for item in dbsession().query(Metric).all()]: - self.crud_service.add_metric(metric) - else: - if metric.ok != dbsession().query(Metric).filter(Metric.name == metric.name).one_or_none().ok: - dbsession().query(Metric).filter(Metric.name == metric.name).delete() - self.crud_service.add_metric(metric) + self.crud_service.add_metric(metric) alert = AlertCreateSchema(data=metric.model_dump(), filter='500') if alert.data["name"] not in [item.data["name"] for item in dbsession().query(Alert).all()]: self.write_alert(metric, alert) @@ -131,14 +125,9 @@ async def _fetch_it(self, fetcher: Fetcher): ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, time_delta=timing, ) - if metric.name not in [item.name for item in dbsession().query(Metric).all()]: - self.crud_service.add_metric(metric) - else: - if metric.ok != dbsession().query(Metric).filter(Metric.name == metric.name).one_or_none().ok: - dbsession().query(Metric).filter(Metric.name == metric.name).delete() - self.crud_service.add_metric(metric) + self.crud_service.add_metric(metric) if not metric.ok: - alert = AlertCreateSchema(data=metric, filter=res.status_code) + alert = AlertCreateSchema(data=metric.model_dump(), filter=str(res.status_code)) if alert.data["name"] not in [item.data["name"] for item in dbsession().query(Alert).all()]: self.write_alert(metric, alert) self.scheduler.reschedule_job( diff --git a/pinger_backend/service/session.py b/pinger_backend/service/session.py index c9f648f..29b32b3 100644 --- a/pinger_backend/service/session.py +++ b/pinger_backend/service/session.py @@ -1,7 +1,7 @@ from sqlalchemy import create_engine from sqlalchemy.orm import Session, declarative_base, sessionmaker -from .settings import get_settings as db_settings +from settings import get_settings as db_settings def dbsession() -> Session: diff --git a/pinger_backend/service/settings.py b/pinger_backend/service/settings.py deleted file mode 100644 index 40d2317..0000000 --- a/pinger_backend/service/settings.py +++ /dev/null @@ -1,15 +0,0 @@ -from functools import lru_cache - -from pydantic import ConfigDict, HttpUrl -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" - BOT_URL: HttpUrl = "http://127.0.0.1:8001" - model_config = ConfigDict(case_sensitive=True, env_file="../.env", extra="ignore") - - -@lru_cache() -def get_settings(): - return Settings() diff --git a/pinger_backend/settings.py b/pinger_backend/settings.py deleted file mode 100644 index f44fe64..0000000 --- a/pinger_backend/settings.py +++ /dev/null @@ -1,18 +0,0 @@ -from functools import lru_cache - -from dotenv import load_dotenv -from pydantic import ConfigDict, PostgresDsn -from pydantic_settings import BaseSettings - - -load_dotenv(verbose=True) - - -class Settings(BaseSettings): - DB_DSN: PostgresDsn - model_config = ConfigDict(case_sensitive=True, env_file="../.env", extra="ignore") - - -@lru_cache() -def get_settings(): - return Settings() diff --git a/aciniformes_backend/settings.py b/settings.py similarity index 64% rename from aciniformes_backend/settings.py rename to settings.py index 95e3cd1..daa5850 100644 --- a/aciniformes_backend/settings.py +++ b/settings.py @@ -1,11 +1,13 @@ from functools import lru_cache -from pydantic import ConfigDict, PostgresDsn +from pydantic import ConfigDict, PostgresDsn, HttpUrl from pydantic_settings import BaseSettings class Settings(BaseSettings): DB_DSN: PostgresDsn + BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" + BOT_URL: HttpUrl = "http://127.0.0.1:8001" model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") diff --git a/tests/backend/api/conftest.py b/tests/backend/api/conftest.py deleted file mode 100644 index 7f46c4e..0000000 --- a/tests/backend/api/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ -import pytest -from fastapi.testclient import TestClient - -from aciniformes_backend.routes.base import app - - -@pytest.fixture -def client(): - client = TestClient(app) - return client diff --git a/tests/backend/api/test_alert.py b/tests/backend/api/test_alert.py index 0ee149c..2474fec 100644 --- a/tests/backend/api/test_alert.py +++ b/tests/backend/api/test_alert.py @@ -1,138 +1,147 @@ import json -import pytest -from starlette import status import pytest_asyncio +from starlette import status from aciniformes_backend.serivce.alert import PgAlertService from aciniformes_backend.serivce.receiver import PgReceiverService -from aciniformes_backend.settings import get_settings -from copy import deepcopy +from settings import get_settings + alert = { - "data": {"type": "string", "name": "string"}, - "filter": "string", - } + "data": {"type": "string", "name": "string"}, + "filter": "string", +} + @pytest_asyncio.fixture async def this_alert(dbsession): global alert _alert = await PgAlertService(dbsession).create(item=alert) - return _alert + yield _alert class TestAlert: _url = "/alert" settings = get_settings() - def test_post_success(self, client): + def test_post_success(self, crud_client): body = { "data": {"type": "string", "name": "string"}, "filter": "string", } - res = client.post(self._url, json=body) + res = crud_client.post(self._url, json=body) res_body = res.json() assert res.status_code == status.HTTP_200_OK assert res_body["data"] == body["data"] assert res_body["filter"] == body["filter"] - def test_get_by_id_success(self, client, this_alert): - res = client.get(f"{self._url}/{this_alert}") + def test_get_by_id_success(self, crud_client, this_alert): + res = crud_client.get(f"{self._url}/{this_alert}") assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["data"] == alert["data"] assert res_body["filter"] == alert["filter"] - def test_delete_by_id_success(self, client, this_alert): - res = client.delete(f"{self._url}/{this_alert}") + def test_delete_by_id_success(self, crud_client, this_alert): + res = crud_client.delete(f"{self._url}/{this_alert}") assert res.status_code == status.HTTP_200_OK - get = client.get(f"{self._url}/{this_alert}") + get = crud_client.get(f"{self._url}/{this_alert}") assert get.status_code == status.HTTP_404_NOT_FOUND - def test_get_success(self, client, this_alert): - res = client.get(self._url) + def test_get_success(self, crud_client, this_alert): + res = crud_client.get(self._url) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert len(res_body) - get = client.get(f"{self._url}/{this_alert}") + get = crud_client.get(f"{self._url}/{this_alert}") assert get.json() in res_body - def test_patch_by_id_success(self, client, this_alert): + def test_patch_by_id_success(self, crud_client, this_alert): body = { "data": {"type": "string", "name": "string"}, "filter": "string", } - res = client.patch(f"{self._url}/{this_alert}", data=json.dumps(body)) + res = crud_client.patch(f"{self._url}/{this_alert}", data=json.dumps(body)) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["data"] == body["data"] - get = client.get(f"{self._url}/{this_alert}") + get = crud_client.get(f"{self._url}/{this_alert}") assert get.status_code == status.HTTP_200_OK assert get.json() == res_body - def test_get_by_id_not_found(self, client, this_alert): - res = client.get(f"{self._url}/{this_alert+2}") + def test_get_by_id_not_found(self, crud_client, this_alert): + res = crud_client.get(f"{self._url}/{this_alert+2}") assert res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_not_found(self, client, this_alert): + def test_patch_by_id_not_found(self, crud_client, this_alert): body = { "data": {"type": "string", "name": "string"}, "filter": "string", } - res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) + res = crud_client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND -@pytest.fixture -def this_receiver(dbsession): - body = {"id": 4, "url": "string", "method": "post", "receiver_body": {}} - return body +reciever = {"url": "https://google.com", "method": "post", "receiver_body": {}} + + +@pytest_asyncio.fixture +async def this_receiver(dbsession): + global reciever + _reciever = await PgReceiverService(dbsession).create(item=reciever) + yield _reciever class TestReceiver: _url = "/receiver" - def test_post_success(self, client): - body = {"url": "string", "method": "post", "receiver_body": {}} - res = client.post(self._url, json=body) + def test_post_success(self, crud_client): + body = {"url": "https://google.com", "method": "post", "receiver_body": {}} + res = crud_client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["url"] == body["url"] assert res_body["receiver_body"] == body["receiver_body"] - def test_get_by_id_success(self, client, this_receiver): - res = client.get(f"{self._url}/{this_receiver['id']}") + def test_get_by_id_success(self, crud_client, this_receiver): + res = crud_client.get(f"{self._url}/{this_receiver}") assert res.status_code == status.HTTP_200_OK res_body = res.json() - assert res_body["url"] == this_receiver["url"] - assert res_body["receiver_body"] == this_receiver["receiver_body"] + assert res_body["url"] == reciever["url"] + assert res_body["receiver_body"] == reciever["receiver_body"] - def test_delete_by_id_success(self, client, this_receiver): - res = client.delete(f"{self._url}/{this_receiver['id']}") + def test_delete_by_id_success(self, crud_client, this_receiver): + res = crud_client.delete(f"{self._url}/{this_receiver}") assert res.status_code == status.HTTP_200_OK - assert self.s.repository[this_receiver["id"]] is None + res = crud_client.get(f"{self._url}/{this_receiver}") + assert res.status_code == status.HTTP_404_NOT_FOUND - def test_get_success(self, client, this_receiver): - res = client.get(self._url) + def test_get_success(self, crud_client, this_receiver): + res = crud_client.get(self._url) assert res.status_code == status.HTTP_200_OK assert len(res.json()) + get = crud_client.get(f"{self._url}/{this_receiver}") + assert get.json() in res.json() - def test_patch_by_id_success(self, client, this_receiver): - body = {"url": "sdasd", "method": "post", "receiver_body": {}} - res = client.patch( - f"{self._url}/{this_receiver['id']}", + def test_patch_by_id_success(self, crud_client, this_receiver): + body = {"url": "https://google.ru", "method": "post", "receiver_body": {}} + res = crud_client.patch( + f"{self._url}/{this_receiver}", data=json.dumps(body), ) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["url"] == body["url"] assert res_body["receiver_body"] == body["receiver_body"] + get = crud_client.get(f"{self._url}/{this_receiver}") + assert get.json() == res.json() - def test_get_by_id_not_found(self, client): - res = client.get(f"{self._url}/{888}") + def test_get_by_id_not_found(self, crud_client): + res = crud_client.get(f"{self._url}/{888}") assert res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_not_found(self, client): - body = {"url": "sdasd", "method": "post", "receiver_body": {}} - res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) + def test_patch_by_id_not_found(self, crud_client): + body = {"url": "https://nf.nf", "method": "post", "receiver_body": {}} + res = crud_client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index f056d33..3fe16d3 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -1,27 +1,30 @@ import json +from copy import deepcopy -import pytest +import pytest_asyncio from starlette import status from aciniformes_backend.serivce.fetcher import PgFetcherService -@pytest.fixture -def this_fetcher(dbsession): - body = { - "id": 6, - "type_": "pinger_backend", - "address": "https://www.python.org", - "fetch_data": "string", - "delay_ok": 30, - "delay_fail": 40, - } - return body + +fetcher = { + "type_": "ping", + "address": "https://www.python.org", + "fetch_data": "string", + "delay_ok": 30, + "delay_fail": 40, +} + + +@pytest_asyncio.fixture +async def this_fetcher(dbsession): + yield await PgFetcherService(dbsession).create(item=fetcher) class TestFetcher: _url = "/fetcher" - def test_post_success(self, client): + def test_post_success(self, crud_client): body = { "type_": "get", "address": "string", @@ -29,27 +32,33 @@ def test_post_success(self, client): "delay_ok": 300, "delay_fail": 30, } - res = client.post(self._url, json=body) + res = crud_client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["id"] is not None - def test_get_by_id_success(self, client, this_fetcher): - res = client.get(f"{self._url}/{this_fetcher['id']}") + def test_get_by_id_success(self, crud_client, this_fetcher): + res = crud_client.get(f"{self._url}/{this_fetcher}") assert res.status_code == status.HTTP_200_OK - assert res.json()["address"] == this_fetcher["address"] + _new_fetcher = deepcopy(fetcher) + for k, v in _new_fetcher.items(): + assert v == res.json()[k] - def test_delete_by_id_success(self, client, this_fetcher): - res = client.delete(f"{self._url}/{this_fetcher['id']}") + def test_delete_by_id_success(self, crud_client, this_fetcher): + res = crud_client.delete(f"{self._url}/{this_fetcher}") assert res.status_code == status.HTTP_200_OK - assert self.s.repository[this_fetcher["id"]] is None + res = crud_client.get(f"{self._url}/{this_fetcher}") + assert res.status_code == status.HTTP_404_NOT_FOUND - def test_get_success(self, client, this_fetcher): - res = client.get(self._url) + def test_get_success(self, crud_client, this_fetcher): + res = crud_client.get(self._url) assert res.status_code == status.HTTP_200_OK assert len(res.json()) + get = crud_client.get(f"{self._url}/{this_fetcher}") + assert get.status_code == status.HTTP_200_OK + assert get.json() in res.json() - def test_patch_by_id_success(self, client, this_fetcher): + def test_patch_by_id_success(self, crud_client, this_fetcher): body = { "type_": "post", "address": "https://api.test.profcomff.com/services/category", @@ -57,20 +66,24 @@ def test_patch_by_id_success(self, client, this_fetcher): "delay_ok": 300, "delay_fail": 30, } - res = client.patch( - f"{self._url}/{this_fetcher['id']}", + res = crud_client.patch( + f"{self._url}/{this_fetcher}", json=body, ) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["address"] == body["address"] assert res_body["type_"] == body["type_"] + res = crud_client.get(f"{self._url}/{this_fetcher}") + assert res.status_code == status.HTTP_200_OK + for k, v in body.items(): + assert v == res.json()[k] - def test_get_by_id_not_found(self, client): - res = client.get(f"{self._url}/{888}") + def test_get_by_id_not_found(self, crud_client): + res = crud_client.get(f"{self._url}/{888}") assert res.status_code == status.HTTP_404_NOT_FOUND - def test_patch_by_id_not_found(self, client): + def test_patch_by_id_not_found(self, crud_client): body = { "type_": "post", "address": "https://api.test.profcomff.com/services/category", @@ -78,5 +91,5 @@ def test_patch_by_id_not_found(self, client): "delay_ok": 300, "delay_fail": 30, } - res = client.patch(f"{self._url}/{888}", data=json.dumps(body)) + res = crud_client.patch(f"{self._url}/{888}", data=json.dumps(body)) assert res.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/backend/api/test_metric.py b/tests/backend/api/test_metric.py index 5ecb929..b2312c8 100644 --- a/tests/backend/api/test_metric.py +++ b/tests/backend/api/test_metric.py @@ -1,22 +1,23 @@ - -import pytest +import pytest_asyncio from starlette import status + from aciniformes_backend.serivce.metric import PgMetricService -@pytest.fixture -def this_metric(dbsession): - body = {"id": 5, "name": "string", "ok": True, "time_delta": 0} - return body +metric = {"name": "string", "ok": True, "time_delta": 0} + + +@pytest_asyncio.fixture +async def this_metric(dbsession): + yield await PgMetricService(dbsession).create(item=metric) class TestMetric: _url = "/metric" - def test_post_success(self, client): + def test_post_success(self, crud_client): body = {"name": "string", "ok": True, "time_delta": 0} - print(self._url) - res = client.post(self._url, json=body) + res = crud_client.post(self._url, json=body) assert res.status_code == status.HTTP_200_OK res_body = res.json() assert res_body["id"] is not None @@ -24,16 +25,20 @@ def test_post_success(self, client): assert res_body["ok"] == body["ok"] assert res_body["time_delta"] == body["time_delta"] - def test_get_by_id_success(self, client, this_metric): - res = client.get(f"{self._url}/{this_metric['id']}") + def test_get_by_id_success(self, crud_client, this_metric): + res = crud_client.get(f"{self._url}/{this_metric}") assert res.status_code == status.HTTP_200_OK - assert res.json()["name"] == this_metric["name"] + for k, v in metric.items(): + assert v == res.json()[k] - def test_get_success(self, client, this_metric): - res = client.get(self._url) + def test_get_success(self, crud_client, this_metric): + res = crud_client.get(self._url) assert res.status_code == status.HTTP_200_OK assert len(res.json()) + get = crud_client.get(f"{self._url}/{this_metric}") + assert res.status_code == status.HTTP_200_OK + assert get.json() in res.json() - def test_get_by_id_not_found(self, client): - res = client.get(f"{self._url}/{333}") + def test_get_by_id_not_found(self, crud_client, this_metric): + res = crud_client.get(f"{self._url}/{this_metric+2}") assert res.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/backend/service/conftest.py b/tests/backend/service/conftest.py deleted file mode 100644 index 79caade..0000000 --- a/tests/backend/service/conftest.py +++ /dev/null @@ -1,30 +0,0 @@ -import pytest - -from aciniformes_backend.serivce import alert_service, fetcher_service, metric_service, receiver_service - -@pytest.fixture -def pg_alert_service(pg_config): - s = alert_service() - assert s.session is not None - yield s - - -@pytest.fixture -def pg_fetcher_service(pg_config): - s = fetcher_service() - assert s.session is not None - yield s - - -@pytest.fixture -def pg_receiver_service(pg_config): - s = receiver_service() - assert s.session is not None - yield s - - -@pytest.fixture -def pg_metric_service(pg_config): - s = metric_service() - assert s.session is not None - yield s diff --git a/tests/backend/service/test_alert_serivce.py b/tests/backend/service/test_alert_serivce.py index 9b582d2..3a0e326 100644 --- a/tests/backend/service/test_alert_serivce.py +++ b/tests/backend/service/test_alert_serivce.py @@ -1,4 +1,3 @@ - import pytest import sqlalchemy @@ -6,18 +5,20 @@ from aciniformes_backend.models import Alert, Receiver from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.alert.reciever import CreateSchema as ReceiverCreateSchema +from aciniformes_backend.serivce.alert import PgAlertService +from aciniformes_backend.serivce.receiver import PgReceiverService @pytest.fixture def receiver_schema(): - body = {"url": "string", "method": "post", "receiver_body": {}} + body = {"url": "https://google.com", "method": "post", "receiver_body": {}} schema = ReceiverCreateSchema(**body) return schema @pytest.fixture def db_receiver(dbsession, receiver_schema): - q = sqlalchemy.insert(Receiver).values(**receiver_schema.dict(exclude_unset=True)).returning(Receiver) + q = sqlalchemy.insert(Receiver).values(**receiver_schema.model_dump(exclude_unset=True)).returning(Receiver) receiver = dbsession.execute(q).scalar() dbsession.flush() yield receiver @@ -38,7 +39,7 @@ def alert_schema(receiver_schema): @pytest.fixture def db_alert(db_receiver, dbsession, alert_schema): - q = sqlalchemy.insert(Alert).values(**alert_schema.dict(exclude_unset=True)).returning(Alert) + q = sqlalchemy.insert(Alert).values(**alert_schema.model_dump(exclude_unset=True)).returning(Alert) alert = dbsession.execute(q).scalar() dbsession.flush() yield alert @@ -49,68 +50,70 @@ def db_alert(db_receiver, dbsession, alert_schema): class TestReceiverService: @pytest.mark.asyncio - async def test_create(self, pg_receiver_service, receiver_schema, dbsession): - res = await pg_receiver_service.create(receiver_schema.dict()) + async def test_create(self, receiver_schema, dbsession): + res = await PgReceiverService(dbsession).create(item=receiver_schema.model_dump()) assert res is not None assert type(res) == int q = dbsession.query(Receiver).filter(Receiver.id_ == res).one_or_none() assert q is not None @pytest.mark.asyncio - async def test_get_all(self, pg_receiver_service, db_receiver, db_alert): - res = await pg_receiver_service.get_all() + async def test_get_all(self, db_receiver, dbsession): + res = await PgReceiverService(dbsession).get_all() assert len(res) assert type(res) is list assert type(res[0]) is Receiver @pytest.mark.asyncio - async def test_get_by_id(self, pg_receiver_service, db_receiver): - res = await pg_receiver_service.get_by_id(db_receiver.id_) + async def test_get_by_id(self, db_receiver, dbsession): + res = await PgReceiverService(dbsession).get_by_id(db_receiver.id_) assert res is not None assert res.url == db_receiver.url with pytest.raises(exc.ObjectNotFound): - await pg_receiver_service.get_by_id(db_receiver.id_ + 1000) + await PgReceiverService(dbsession).get_by_id(db_receiver.id_ + 1000) @pytest.mark.asyncio - async def test_delete(self, pg_receiver_service, db_receiver): - await pg_receiver_service.delete(db_receiver.id_) + async def test_delete(self, db_receiver, dbsession): + await PgReceiverService(dbsession).delete(db_receiver.id_) @pytest.mark.asyncio - async def test_update(self, pg_receiver_service, db_receiver, dbsession): - res = await pg_receiver_service.update(db_receiver.id_, {"url": "Alex", "method": "post", "receiver_body": {}}) + async def test_update(self, db_receiver, dbsession): + res = await PgReceiverService(dbsession).update( + db_receiver.id_, {"url": "Alex", "method": "post", "receiver_body": {}} + ) assert res.url == "Alex" assert res.receiver_body == {} class TestAlertService: @pytest.mark.asyncio - async def test_create(self, pg_alert_service, alert_schema, db_receiver): - res = await pg_alert_service.create( - alert_schema.dict(exclude_unset=True), + async def test_create(self, alert_schema, db_receiver, dbsession): + res = await PgAlertService(dbsession).create( + alert_schema.model_dump(exclude_unset=True), ) assert type(res) == int @pytest.mark.asyncio - async def test_get_all(self, pg_alert_service, db_alert): - res = await pg_alert_service.get_all() + async def test_get_all(self, db_alert, dbsession): + res = await PgAlertService(dbsession).get_all() assert len(res) assert type(res) is list assert type(res[0]) is Alert @pytest.mark.asyncio - async def test_get_by_id(self, pg_alert_service, db_alert): - res = await pg_alert_service.get_by_id(db_alert.id_) + async def test_get_by_id(self, dbsession, db_alert): + res = await PgAlertService(dbsession).get_by_id(db_alert.id_) assert res is not None assert res.data == db_alert.data assert res.filter == db_alert.filter with pytest.raises(exc.ObjectNotFound): - await pg_alert_service.get_by_id(db_alert.id_ + 1000) + await PgAlertService(dbsession).get_by_id(db_alert.id_ + 1000) @pytest.mark.asyncio - async def test_delete(self, pg_alert_service, db_alert): - await pg_alert_service.delete(db_alert.id_) + async def test_delete(self, dbsession, db_alert): + await PgAlertService(dbsession).delete(db_alert.id_) @pytest.mark.asyncio - async def test_update(self, pg_alert_service, db_alert): - res = await pg_alert_service.update(db_alert.id_, {"data": {"type": "stig", "name": "stig"}}) + async def test_update(self, dbsession, db_alert): + res = await PgAlertService(dbsession).update(db_alert.id_, {"data": {"type": "stig", "name": "stig"}}) assert res.data == {"type": "stig", "name": "stig"} diff --git a/tests/backend/service/test_fetcher_service.py b/tests/backend/service/test_fetcher_service.py index e2469c2..e54a210 100644 --- a/tests/backend/service/test_fetcher_service.py +++ b/tests/backend/service/test_fetcher_service.py @@ -3,6 +3,7 @@ from aciniformes_backend.models import Fetcher from aciniformes_backend.routes.fetcher import CreateSchema as FetcherCreateSchema +from aciniformes_backend.serivce.fetcher import PgFetcherService @pytest.fixture @@ -32,30 +33,30 @@ def db_fetcher(dbsession, fetcher_schema): class TestFetcherService: @pytest.mark.asyncio - async def test_create(self, pg_fetcher_service, fetcher_schema, dbsession): - res = await pg_fetcher_service.create(fetcher_schema.dict(exclude_unset=True)) + async def test_create(self, dbsession, fetcher_schema): + res = await PgFetcherService(dbsession).create(fetcher_schema.model_dump(exclude_unset=True)) assert res is not None assert type(res) is int q = dbsession.scalar(sqlalchemy.select(Fetcher).where(Fetcher.id_ == res)) assert q is not None @pytest.mark.asyncio - async def test_get_all(self, pg_fetcher_service, db_fetcher): - res = await pg_fetcher_service.get_all() + async def test_get_all(self, dbsession, db_fetcher): + res = await PgFetcherService(dbsession).get_all() assert type(res) is list assert type(res[0]) is Fetcher @pytest.mark.asyncio - async def test_get_by_id(self, pg_fetcher_service, db_fetcher): - res = await pg_fetcher_service.get_by_id(db_fetcher.id_) + async def test_get_by_id(self, dbsession, db_fetcher): + res = await PgFetcherService(dbsession).get_by_id(db_fetcher.id_) assert res.address == db_fetcher.address assert res.type_ == db_fetcher.type_ @pytest.mark.asyncio - async def test_delete(self, pg_fetcher_service, db_fetcher): - await pg_fetcher_service.delete(db_fetcher.id_) + async def test_delete(self, dbsession, db_fetcher): + await PgFetcherService(dbsession).delete(db_fetcher.id_) @pytest.mark.asyncio - async def test_update(self, pg_fetcher_service, db_fetcher): - res = await pg_fetcher_service.update(db_fetcher.id_, {"type_": "post"}) + async def test_update(self, dbsession, db_fetcher): + res = await PgFetcherService(dbsession).update(db_fetcher.id_, {"type_": "post"}) assert res.type_ == "post" diff --git a/tests/backend/service/test_metric_service.py b/tests/backend/service/test_metric_service.py index bcb7302..3969dbe 100644 --- a/tests/backend/service/test_metric_service.py +++ b/tests/backend/service/test_metric_service.py @@ -3,6 +3,7 @@ from aciniformes_backend.models import Metric from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema +from aciniformes_backend.serivce.metric import PgMetricService @pytest.fixture @@ -25,21 +26,21 @@ def db_metric(dbsession, metric_schema): class TestMetricService: @pytest.mark.asyncio - async def test_create(self, pg_metric_service, metric_schema, dbsession): - res = await pg_metric_service.create(metric_schema.dict(exclude_unset=True)) + async def test_create(self, metric_schema, dbsession): + res = await PgMetricService(dbsession).create(metric_schema.model_dump(exclude_unset=True)) assert res is not None assert type(res) is int q = dbsession.scalar(sqlalchemy.select(Metric).where(Metric.id_ == res)) assert q is not None @pytest.mark.asyncio - async def test_get_all(self, pg_metric_service): - res = await pg_metric_service.get_all() + async def test_get_all(self, dbsession): + res = await PgMetricService(dbsession).get_all() assert type(res) is list assert type(res[0]) is Metric @pytest.mark.asyncio - async def test_get_by_id(self, pg_metric_service, db_metric): - res = await pg_metric_service.get_by_id(db_metric.id_) + async def test_get_by_id(self, dbsession, db_metric): + res = await PgMetricService(dbsession).get_by_id(db_metric.id_) assert res.name == db_metric.name assert res.ok == db_metric.ok diff --git a/tests/backend/conftest.py b/tests/conftest.py similarity index 82% rename from tests/backend/conftest.py rename to tests/conftest.py index a98f810..99a6d25 100644 --- a/tests/backend/conftest.py +++ b/tests/conftest.py @@ -5,8 +5,7 @@ from aciniformes_backend.models.base import BaseModel from aciniformes_backend.routes.base import app -from aciniformes_backend.settings import get_settings -from pinger_backend.service.settings import get_settings as settings_backend +from settings import get_settings @pytest.fixture(scope="session") @@ -32,10 +31,9 @@ def dbsession(engine, tables): connection.close() - @pytest.fixture -def client(fake_config): +def crud_client(): client = TestClient(app) - settings = settings_backend() + settings = get_settings() settings.BACKEND_URL = "http://testserver" return client diff --git a/tests/ping_service/conftest.py b/tests/ping_service/conftest.py deleted file mode 100644 index 008063d..0000000 --- a/tests/ping_service/conftest.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import Session, sessionmaker - -from aciniformes_backend.models.base import BaseModel -from aciniformes_backend.routes import app -from aciniformes_backend.settings import get_settings - - -@pytest.fixture(scope="session") -def crud_client(): - client = TestClient(app) - return client - - -@pytest.fixture(scope="session") -def dbsession() -> Session: - settings = get_settings() - engine = create_engine(str(settings.DB_DSN), execution_options={"isolation_level": "AUTOCOMMIT"}) - TestingSessionLocal = sessionmaker(bind=engine) - BaseModel.metadata.drop_all(bind=engine) - BaseModel.metadata.create_all(bind=engine) - yield TestingSessionLocal() diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index c99ea96..7a421e1 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -1,11 +1,11 @@ import pytest + from pinger_backend.service.scheduler import ApSchedulerService, CrudService @pytest.fixture -def pg_scheduler_service(pg_config): +def pg_scheduler_service(): s = ApSchedulerService(CrudService()) s.backend_url = "http://testserver" assert s.scheduler is not dict yield s - diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 1633fe8..3e4d5fb 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -9,7 +9,7 @@ def fetcher_obj(): yield Fetcher( **{ "type_": "ping", - "address": "localhost", + "address": "https://www.google.com", "fetch_data": "string", "delay_ok": 30, "delay_fail": 40, @@ -19,13 +19,13 @@ def fetcher_obj(): class TestSchedulerService: @pytest.mark.asyncio - async def test_add_fetcher_success(self, pg_scheduler_service, fake_crud_service, fetcher_obj): + async def test_add_fetcher_success(self, pg_scheduler_service, fetcher_obj): pg_scheduler_service.add_fetcher(fetcher_obj) fetchers = pg_scheduler_service.get_jobs() assert f'{fetcher_obj.address} None' in fetchers @pytest.mark.asyncio - async def test_delete_fetcher(self, pg_scheduler_service, fake_crud_service, fetcher_obj): + async def test_delete_fetcher(self, pg_scheduler_service, fetcher_obj): pg_scheduler_service.add_fetcher(fetcher_obj) fetchers = pg_scheduler_service.get_jobs() assert f"{fetcher_obj.address} {fetcher_obj.create_ts}" in fetchers @@ -35,12 +35,12 @@ async def test_delete_fetcher(self, pg_scheduler_service, fake_crud_service, fet assert fetcher_obj not in fetchers @pytest.mark.asyncio - async def test_get_jobs(self, pg_scheduler_service, fake_crud_service): + async def test_get_jobs(self, pg_scheduler_service): res = pg_scheduler_service.get_jobs() assert type(res) is list @pytest.mark.asyncio - async def test_start_already_started(self, pg_scheduler_service, fake_crud_service, crud_client): + async def test_start_already_started(self, pg_scheduler_service, crud_client): await pg_scheduler_service.start() fail = False try: @@ -51,7 +51,7 @@ async def test_start_already_started(self, pg_scheduler_service, fake_crud_servi pg_scheduler_service.stop() @pytest.mark.asyncio - async def test_ping_fail(self, pg_scheduler_service, fetcher_obj, fake_crud_service, dbsession): + async def test_ping_fail(self, pg_scheduler_service, fetcher_obj, dbsession): fetcher = Fetcher( **{ "type_": "ping", @@ -65,5 +65,5 @@ async def test_ping_fail(self, pg_scheduler_service, fetcher_obj, fake_crud_serv pg_scheduler_service._fetch_it(fetcher) metrics = dbsession.query(Metric).all() for metric in metrics: - if metric['name'] == fetcher.address: + if metric.name == fetcher.address: assert not metric['ok'] From ca789a10c3c4f1ea76de8937ee75b3d3243f0086 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Sun, 23 Jul 2023 19:01:17 +0300 Subject: [PATCH 46/53] lint --- Makefile | 17 ++++++++--------- settings.py | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index b3b2278..46fa713 100644 --- a/Makefile +++ b/Makefile @@ -7,16 +7,15 @@ configure: venv venv: python3.11 -m venv venv +atomic-format: + autoflake -r --in-place --remove-all-unused-imports ./$(module) + isort ./$(module) + black ./$(module) + format: - autoflake -r --in-place --remove-all-unused-imports ./pinger_backend - autoflake -r --in-place --remove-all-unused-imports ./aciniformes_backend - autoflake -r --in-place --remove-all-unused-imports ./tests - isort ./pinger_backend - isort ./aciniformes_backend - isort ./tests - black ./pinger_backend - black ./aciniformes_backend - black ./tests + make atomic-format module=pinger_backend + make atomic-format module=aciniformes_backend + make atomic-format module=settings.py db: docker run -d -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --name db-pinger_backend postgres:15 diff --git a/settings.py b/settings.py index daa5850..48569ad 100644 --- a/settings.py +++ b/settings.py @@ -1,6 +1,6 @@ from functools import lru_cache -from pydantic import ConfigDict, PostgresDsn, HttpUrl +from pydantic import ConfigDict, HttpUrl, PostgresDsn from pydantic_settings import BaseSettings From 7bef0c419aaee444ef1019f017a7e0fe7e2a7376 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Sun, 23 Jul 2023 22:55:33 +0300 Subject: [PATCH 47/53] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=84=D0=B5=D1=82=D1=87=20=D1=84=D0=B5=D1=82=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D0=B2=20=D0=BF=D0=BE=D0=BE=20=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D1=8E=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BB=D0=BE=D0=B3=D0=B3=D0=B8?= =?UTF-8?q?=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aciniformes_backend/routes/alert/reciever.py | 4 +- logging_pinger.conf | 21 +++ pinger_backend/__main__.py | 14 +- pinger_backend/service/scheduler.py | 151 +++++++++---------- settings.py | 1 + tests/backend/api/test_fetcher.py | 2 +- tests/ping_service/service/test_scheduler.py | 6 +- 7 files changed, 107 insertions(+), 92 deletions(-) create mode 100644 logging_pinger.conf diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index 040218f..31ff42e 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -1,5 +1,5 @@ -import enum import logging +from enum import Enum from fastapi import APIRouter, Depends from fastapi.exceptions import HTTPException @@ -14,7 +14,7 @@ logger = logging.getLogger(__name__) -class Method(str, enum.Enum): +class Method(str, Enum): POST: str = "post" GET: str = "get" diff --git a/logging_pinger.conf b/logging_pinger.conf new file mode 100644 index 0000000..8432334 --- /dev/null +++ b/logging_pinger.conf @@ -0,0 +1,21 @@ +[loggers] +keys=root + +[handlers] +keys=all + +[formatters] +keys=json + +[logger_root] +level=INFO +handlers=all + +[handler_all] +class=StreamHandler +formatter=json +level=INFO +args=(sys.stdout,) + +[formatter_json] +class=logger.formatter.JSONLogFormatter diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 93a9f90..1edc9ac 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -1,10 +1,18 @@ import asyncio - -from service.crud import CrudService -from service.scheduler import ApSchedulerService +from logging.config import fileConfig +from pathlib import Path from settings import get_settings +from .service.crud import CrudService +from .service.scheduler import ApSchedulerService + + +path = Path(__file__).resolve().parents[1] + + +fileConfig(f"{path}/logging_pinger.conf") + if __name__ == "__main__": loop = asyncio.new_event_loop() diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 746996d..f1de659 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -1,5 +1,7 @@ import time from abc import ABC +from contextlib import asynccontextmanager +from typing import AsyncIterator import ping3 import requests @@ -9,6 +11,7 @@ from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from pinger_backend.exceptions import AlreadyRunning, AlreadyStopped +from settings import get_settings from .crud import CrudService from .session import dbsession @@ -16,8 +19,9 @@ class ApSchedulerService(ABC): scheduler = AsyncIOScheduler() + settings = get_settings() backend_url = str - fetchers = set + fetchers: list def __init__(self, crud_service: CrudService): self.crud_service = crud_service @@ -40,6 +44,12 @@ def get_jobs(self): async def start(self): if self.scheduler.running: raise AlreadyRunning + self.scheduler.add_job( + self._fetcher_update_job, + id="check_fetchers", + seconds=self.settings.FETCHERS_UPDATE_DELAY_IN_SECONDS, + trigger="interval", + ) self.fetchers = dbsession().query(Fetcher).all() self.scheduler.start() for fetcher in self.fetchers: @@ -53,95 +63,21 @@ def stop(self): job.remove() self.scheduler.shutdown() - def write_alert(self, metric_log: MetricCreateSchema, alert: AlertCreateSchema): + def write_alert(self, alert: AlertCreateSchema): receivers = dbsession().query(Receiver).all() session = dbsession() alert = Alert(**alert.model_dump(exclude_none=True)) session.add(alert) session.flush() for receiver in receivers: - receiver.receiver_body['text'] = metric_log - requests.request(method="POST", url=receiver.url, data=receiver.receiver_body) + requests.request(method=receiver.method, url=receiver.url, data=receiver.receiver_body) @staticmethod - def _parse_timedelta(fetcher: Fetcher): + def _parse_timedelta(fetcher: Fetcher) -> tuple[int, int]: return fetcher.delay_ok, fetcher.delay_fail - async def _fetch_it(self, fetcher: Fetcher): - prev = time.time() - res = None - try: - match fetcher.type_: - case FetcherType.GET: - res = requests.request(method="GET", url=fetcher.address) - case FetcherType.POST: - res = requests.request(method="POST", url=fetcher.address, data=fetcher.fetch_data) - case FetcherType.PING: - res = ping3.ping(fetcher.address) - - except Exception: - cur = time.time() - timing = cur - prev - metric = MetricCreateSchema( - name=fetcher.address, - ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, - time_delta=timing, - ) - self.crud_service.add_metric(metric) - alert = AlertCreateSchema(data=metric.model_dump(), filter='500') - if alert.data["name"] not in [item.data["name"] for item in dbsession().query(Alert).all()]: - self.write_alert(metric, alert) - self.scheduler.reschedule_job( - f"{fetcher.address} {fetcher.create_ts}", - seconds=fetcher.delay_fail, - trigger="interval", - ) - - jobs = [job.id for job in self.scheduler.get_jobs()] - old_fetchers = self.fetchers - new_fetchers = dbsession().query(Fetcher).all() - - # Проверка на удаление фетчера - for fetcher in old_fetchers: - if (fetcher.address not in [ftch.address for ftch in new_fetchers]) and ( - f"{fetcher.address} {fetcher.create_ts}" in jobs - ): - self.scheduler.remove_job(job_id=f"{fetcher.address} {fetcher.create_ts}") - - jobs = [job.id for job in self.scheduler.get_jobs()] - # Проверка на добавление нового фетчера - for fetcher in new_fetchers: - if (f"{fetcher.address} {fetcher.create_ts}" not in jobs) and ( - fetcher.address not in [ftch.address for ftch in old_fetchers] - ): - self.add_fetcher(fetcher) - self.fetchers.append(fetcher) - - return - cur = time.time() - timing = cur - prev - metric = MetricCreateSchema( - name=fetcher.address, - ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, - time_delta=timing, - ) - self.crud_service.add_metric(metric) - if not metric.ok: - alert = AlertCreateSchema(data=metric.model_dump(), filter=str(res.status_code)) - if alert.data["name"] not in [item.data["name"] for item in dbsession().query(Alert).all()]: - self.write_alert(metric, alert) - self.scheduler.reschedule_job( - f"{fetcher.address} {fetcher.create_ts}", - seconds=fetcher.delay_fail, - trigger="interval", - ) - else: - self.scheduler.reschedule_job( - f"{fetcher.address} {fetcher.create_ts}", - seconds=fetcher.delay_ok, - trigger="interval", - ) - + @asynccontextmanager + async def __update_fetchers(self) -> AsyncIterator[None]: jobs = [job.id for job in self.scheduler.get_jobs()] old_fetchers = self.fetchers new_fetchers = dbsession().query(Fetcher).all() @@ -153,11 +89,64 @@ async def _fetch_it(self, fetcher: Fetcher): ): self.scheduler.remove_job(job_id=f"{fetcher.address} {fetcher.create_ts}") - # Проверка на добавление нового фетчера jobs = [job.id for job in self.scheduler.get_jobs()] + # Проверка на добавление нового фетчера for fetcher in new_fetchers: if (f"{fetcher.address} {fetcher.create_ts}" not in jobs) and ( fetcher.address not in [ftch.address for ftch in old_fetchers] ): self.add_fetcher(fetcher) self.fetchers.append(fetcher) + yield + self.scheduler.reschedule_job( + "check_fetchers", seconds=self.settings.FETCHERS_UPDATE_DELAY_IN_SECONDS, trigger="interval" + ) + + async def _fetcher_update_job(self) -> None: + async with self.__update_fetchers(): + pass + + @staticmethod + def create_metric(prev: float, fetcher: Fetcher, res: requests.Response) -> MetricCreateSchema: + cur = time.time() + timing = cur - prev + return MetricCreateSchema( + name=fetcher.address, + ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, + time_delta=timing, + ) + + def _reschedule_job(self, fetcher: Fetcher, ok: bool): + self.scheduler.reschedule_job( + f"{fetcher.address} {fetcher.create_ts}", + seconds=fetcher.delay_ok if ok else fetcher.delay_fail, + trigger="interval", + ) + + def _process_fail(self, fetcher: Fetcher, metric: MetricCreateSchema, res: requests.Response | None) -> None: + alert = AlertCreateSchema(data=metric.model_dump(), filter="500" if res is None else str(res.status_code)) + self.write_alert(alert) + self._reschedule_job(fetcher, False) + + async def _fetch_it(self, fetcher: Fetcher): + prev = time.time() + res = None + try: + match fetcher.type_: + case FetcherType.GET: + res = requests.get(url=fetcher.address) + case FetcherType.POST: + res = requests.post(url=fetcher.address, data=fetcher.fetch_data) + case FetcherType.PING: + res = ping3.ping(fetcher.address) + except Exception: + metric = ApSchedulerService.create_metric(prev, fetcher, res) + self.crud_service.add_metric(metric) + self._process_fail(fetcher, metric, None) + else: + metric = ApSchedulerService.create_metric(prev, fetcher, res) + self.crud_service.add_metric(metric) + if not metric.ok: + self._process_fail(fetcher, metric, res) + else: + self._reschedule_job(fetcher, True) diff --git a/settings.py b/settings.py index 48569ad..00137de 100644 --- a/settings.py +++ b/settings.py @@ -8,6 +8,7 @@ class Settings(BaseSettings): DB_DSN: PostgresDsn BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" BOT_URL: HttpUrl = "http://127.0.0.1:8001" + FETCHERS_UPDATE_DELAY_IN_SECONDS: int = 10 model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") diff --git a/tests/backend/api/test_fetcher.py b/tests/backend/api/test_fetcher.py index 3fe16d3..0dd6947 100644 --- a/tests/backend/api/test_fetcher.py +++ b/tests/backend/api/test_fetcher.py @@ -27,7 +27,7 @@ class TestFetcher: def test_post_success(self, crud_client): body = { "type_": "get", - "address": "string", + "address": "https://google.com", "fetch_data": "string", "delay_ok": 300, "delay_fail": 30, diff --git a/tests/ping_service/service/test_scheduler.py b/tests/ping_service/service/test_scheduler.py index 3e4d5fb..5f9b7ce 100644 --- a/tests/ping_service/service/test_scheduler.py +++ b/tests/ping_service/service/test_scheduler.py @@ -42,12 +42,8 @@ async def test_get_jobs(self, pg_scheduler_service): @pytest.mark.asyncio async def test_start_already_started(self, pg_scheduler_service, crud_client): await pg_scheduler_service.start() - fail = False - try: + with pytest.raises(AlreadyRunning): await pg_scheduler_service.start() - except AlreadyRunning: - fail = True - assert fail pg_scheduler_service.stop() @pytest.mark.asyncio From ea6408012422c80f4e0748afe174c102453297f3 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Sun, 23 Jul 2023 23:37:16 +0300 Subject: [PATCH 48/53] =?UTF-8?q?=D0=BF=D0=BE=D1=84=D0=B8=D0=BA=D1=81?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BF=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pinger_backend/service/scheduler.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index f1de659..30c0ba4 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -110,9 +110,15 @@ async def _fetcher_update_job(self) -> None: def create_metric(prev: float, fetcher: Fetcher, res: requests.Response) -> MetricCreateSchema: cur = time.time() timing = cur - prev + if fetcher.type_ != FetcherType.PING: + return MetricCreateSchema( + name=fetcher.address, + ok=True if res and (200 <= res.status_code <= 300) else False, + time_delta=timing, + ) return MetricCreateSchema( name=fetcher.address, - ok=True if (res and (200 <= res.status_code <= 300)) or (res == 0) else False, + ok=res is not False and res is not None, time_delta=timing, ) @@ -124,7 +130,10 @@ def _reschedule_job(self, fetcher: Fetcher, ok: bool): ) def _process_fail(self, fetcher: Fetcher, metric: MetricCreateSchema, res: requests.Response | None) -> None: - alert = AlertCreateSchema(data=metric.model_dump(), filter="500" if res is None else str(res.status_code)) + if fetcher.type_ != FetcherType.PING: + alert = AlertCreateSchema(data=metric.model_dump(), filter="500" if res is None else str(res.status_code)) + else: + alert = AlertCreateSchema(data=metric.model_dump(), filter=str(res)) self.write_alert(alert) self._reschedule_job(fetcher, False) From 052e73ce23e5c5e91bbb5217203bc46c8fde5ac4 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Sun, 23 Jul 2023 23:52:39 +0300 Subject: [PATCH 49/53] loop sigint handler --- pinger_backend/__main__.py | 7 +++++++ pinger_backend/service/scheduler.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 1edc9ac..1f9deb5 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -1,4 +1,5 @@ import asyncio +import signal from logging.config import fileConfig from pathlib import Path @@ -14,10 +15,16 @@ fileConfig(f"{path}/logging_pinger.conf") +def sigint_callback(scheduler: ApSchedulerService) -> None: + scheduler.stop() + exit(0) + + if __name__ == "__main__": loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) scheduler = ApSchedulerService(CrudService()) scheduler.backend_url = get_settings().BACKEND_URL + loop.add_signal_handler(signal.SIGINT, callback=lambda: sigint_callback(scheduler)) loop.create_task(scheduler.start()) loop.run_forever() diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 30c0ba4..443906c 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -133,7 +133,8 @@ def _process_fail(self, fetcher: Fetcher, metric: MetricCreateSchema, res: reque if fetcher.type_ != FetcherType.PING: alert = AlertCreateSchema(data=metric.model_dump(), filter="500" if res is None else str(res.status_code)) else: - alert = AlertCreateSchema(data=metric.model_dump(), filter=str(res)) + _filter = "Service Unavailable" if res is False else "Timeout Error" if res is None else "Unknown Error" + alert = AlertCreateSchema(data=metric.model_dump(), filter=_filter) self.write_alert(alert) self._reschedule_job(fetcher, False) From 0ce30a3bdc617007a91f72ed453d419299b7b9ca Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 24 Jul 2023 01:03:01 +0300 Subject: [PATCH 50/53] crud service del --- pinger_backend/__main__.py | 3 +-- pinger_backend/service/crud.py | 28 -------------------------- pinger_backend/service/scheduler.py | 18 ++++++++++------- settings.py | 1 - tests/ping_service/service/conftest.py | 4 ++-- 5 files changed, 14 insertions(+), 40 deletions(-) delete mode 100644 pinger_backend/service/crud.py diff --git a/pinger_backend/__main__.py b/pinger_backend/__main__.py index 1f9deb5..18ac5b7 100644 --- a/pinger_backend/__main__.py +++ b/pinger_backend/__main__.py @@ -5,7 +5,6 @@ from settings import get_settings -from .service.crud import CrudService from .service.scheduler import ApSchedulerService @@ -23,7 +22,7 @@ def sigint_callback(scheduler: ApSchedulerService) -> None: if __name__ == "__main__": loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - scheduler = ApSchedulerService(CrudService()) + scheduler = ApSchedulerService() scheduler.backend_url = get_settings().BACKEND_URL loop.add_signal_handler(signal.SIGINT, callback=lambda: sigint_callback(scheduler)) loop.create_task(scheduler.start()) diff --git a/pinger_backend/service/crud.py b/pinger_backend/service/crud.py deleted file mode 100644 index b7f2b42..0000000 --- a/pinger_backend/service/crud.py +++ /dev/null @@ -1,28 +0,0 @@ -from abc import ABC - -from aciniformes_backend.models import Alert, Fetcher, Metric -from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema -from settings import get_settings - -from .session import dbsession - - -class CrudService(ABC): - backend_url: str - - def __init__(self): - self.backend_url = get_settings().BACKEND_URL - - def get_fetchers(self) -> list[Fetcher]: - return [Fetcher(**d) for d in dbsession().query(Fetcher).all()] - - def add_metric(self, metric: MetricCreateSchema): - session = dbsession() - metric = Metric(**metric.model_dump(exclude_none=True)) - session.add(metric) - session.commit() - session.flush() - return metric - - def get_alerts(self) -> list[Alert]: - return [Alert(**d) for d in dbsession().query(Alert).all()] diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 443906c..fb088a7 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -7,13 +7,12 @@ import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler -from aciniformes_backend.models import Alert, Fetcher, FetcherType, Receiver +from aciniformes_backend.models import Alert, Fetcher, FetcherType, Metric, Receiver from aciniformes_backend.routes.alert.alert import CreateSchema as AlertCreateSchema from aciniformes_backend.routes.mectric import CreateSchema as MetricCreateSchema from pinger_backend.exceptions import AlreadyRunning, AlreadyStopped from settings import get_settings -from .crud import CrudService from .session import dbsession @@ -23,9 +22,6 @@ class ApSchedulerService(ABC): backend_url = str fetchers: list - def __init__(self, crud_service: CrudService): - self.crud_service = crud_service - def add_fetcher(self, fetcher: Fetcher): self.scheduler.add_job( self._fetch_it, @@ -138,6 +134,14 @@ def _process_fail(self, fetcher: Fetcher, metric: MetricCreateSchema, res: reque self.write_alert(alert) self._reschedule_job(fetcher, False) + def add_metric(self, metric: MetricCreateSchema): + session = dbsession() + metric = Metric(**metric.model_dump(exclude_none=True)) + session.add(metric) + session.commit() + session.flush() + return metric + async def _fetch_it(self, fetcher: Fetcher): prev = time.time() res = None @@ -151,11 +155,11 @@ async def _fetch_it(self, fetcher: Fetcher): res = ping3.ping(fetcher.address) except Exception: metric = ApSchedulerService.create_metric(prev, fetcher, res) - self.crud_service.add_metric(metric) + self.add_metric(metric) self._process_fail(fetcher, metric, None) else: metric = ApSchedulerService.create_metric(prev, fetcher, res) - self.crud_service.add_metric(metric) + self.add_metric(metric) if not metric.ok: self._process_fail(fetcher, metric, res) else: diff --git a/settings.py b/settings.py index 00137de..dd49b95 100644 --- a/settings.py +++ b/settings.py @@ -7,7 +7,6 @@ class Settings(BaseSettings): DB_DSN: PostgresDsn BACKEND_URL: HttpUrl = "http://127.0.0.1:8000" - BOT_URL: HttpUrl = "http://127.0.0.1:8001" FETCHERS_UPDATE_DELAY_IN_SECONDS: int = 10 model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="ignore") diff --git a/tests/ping_service/service/conftest.py b/tests/ping_service/service/conftest.py index 7a421e1..27364f8 100644 --- a/tests/ping_service/service/conftest.py +++ b/tests/ping_service/service/conftest.py @@ -1,11 +1,11 @@ import pytest -from pinger_backend.service.scheduler import ApSchedulerService, CrudService +from pinger_backend.service.scheduler import ApSchedulerService @pytest.fixture def pg_scheduler_service(): - s = ApSchedulerService(CrudService()) + s = ApSchedulerService() s.backend_url = "http://testserver" assert s.scheduler is not dict yield s From 7959f583ae128abc87f73706d12868f0d1e63c0d Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 24 Jul 2023 02:49:13 +0300 Subject: [PATCH 51/53] async --- pinger_backend/service/ping.py | 14 ++++++++++++++ pinger_backend/service/scheduler.py | 23 +++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 pinger_backend/service/ping.py diff --git a/pinger_backend/service/ping.py b/pinger_backend/service/ping.py new file mode 100644 index 0000000..6a6b161 --- /dev/null +++ b/pinger_backend/service/ping.py @@ -0,0 +1,14 @@ +import asyncio +from concurrent.futures import ThreadPoolExecutor +from functools import partial +from typing import Literal + +from ping3 import ping as sync_ping + + +thread_pool = ThreadPoolExecutor() + + +async def ping(host: str) -> float | Literal[True, None]: + loop = asyncio.get_event_loop() + return await loop.run_in_executor(thread_pool, partial(sync_ping, host)) diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index fb088a7..18bc8b2 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -3,7 +3,7 @@ from contextlib import asynccontextmanager from typing import AsyncIterator -import ping3 +import aiohttp import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler @@ -13,6 +13,7 @@ from pinger_backend.exceptions import AlreadyRunning, AlreadyStopped from settings import get_settings +from .ping import ping from .session import dbsession @@ -103,13 +104,13 @@ async def _fetcher_update_job(self) -> None: pass @staticmethod - def create_metric(prev: float, fetcher: Fetcher, res: requests.Response) -> MetricCreateSchema: + def create_metric(prev: float, fetcher: Fetcher, res: aiohttp.ClientResponse) -> MetricCreateSchema: cur = time.time() timing = cur - prev if fetcher.type_ != FetcherType.PING: return MetricCreateSchema( name=fetcher.address, - ok=True if res and (200 <= res.status_code <= 300) else False, + ok=True if res and (200 <= res.status <= 300) else False, time_delta=timing, ) return MetricCreateSchema( @@ -125,9 +126,11 @@ def _reschedule_job(self, fetcher: Fetcher, ok: bool): trigger="interval", ) - def _process_fail(self, fetcher: Fetcher, metric: MetricCreateSchema, res: requests.Response | None) -> None: + def _process_fail( + self, fetcher: Fetcher, metric: MetricCreateSchema, res: aiohttp.ClientResponse | None | float + ) -> None: if fetcher.type_ != FetcherType.PING: - alert = AlertCreateSchema(data=metric.model_dump(), filter="500" if res is None else str(res.status_code)) + alert = AlertCreateSchema(data=metric.model_dump(), filter="500" if res is None else str(res.status)) else: _filter = "Service Unavailable" if res is False else "Timeout Error" if res is None else "Unknown Error" alert = AlertCreateSchema(data=metric.model_dump(), filter=_filter) @@ -148,11 +151,15 @@ async def _fetch_it(self, fetcher: Fetcher): try: match fetcher.type_: case FetcherType.GET: - res = requests.get(url=fetcher.address) + async with aiohttp.ClientSession() as session: + async with session.get(url=fetcher.address) as res: + pass case FetcherType.POST: - res = requests.post(url=fetcher.address, data=fetcher.fetch_data) + async with aiohttp.ClientSession() as session: + async with session.post(url=fetcher.address, data=fetcher.fetch_data) as res: + pass case FetcherType.PING: - res = ping3.ping(fetcher.address) + res = await ping(fetcher.address) except Exception: metric = ApSchedulerService.create_metric(prev, fetcher, res) self.add_metric(metric) From 7e1e4254a6ddedb683202b0c0c37d4ffdb15e259 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Mon, 24 Jul 2023 02:51:58 +0300 Subject: [PATCH 52/53] async --- pinger_backend/service/scheduler.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pinger_backend/service/scheduler.py b/pinger_backend/service/scheduler.py index 18bc8b2..4cdec2d 100644 --- a/pinger_backend/service/scheduler.py +++ b/pinger_backend/service/scheduler.py @@ -4,7 +4,6 @@ from typing import AsyncIterator import aiohttp -import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler from aciniformes_backend.models import Alert, Fetcher, FetcherType, Metric, Receiver @@ -60,14 +59,16 @@ def stop(self): job.remove() self.scheduler.shutdown() - def write_alert(self, alert: AlertCreateSchema): + async def write_alert(self, alert: AlertCreateSchema): receivers = dbsession().query(Receiver).all() session = dbsession() alert = Alert(**alert.model_dump(exclude_none=True)) session.add(alert) session.flush() for receiver in receivers: - requests.request(method=receiver.method, url=receiver.url, data=receiver.receiver_body) + async with aiohttp.ClientSession() as s: + async with s.request(method=receiver.method, url=receiver.url, data=receiver.receiver_body): + pass @staticmethod def _parse_timedelta(fetcher: Fetcher) -> tuple[int, int]: @@ -126,7 +127,7 @@ def _reschedule_job(self, fetcher: Fetcher, ok: bool): trigger="interval", ) - def _process_fail( + async def _process_fail( self, fetcher: Fetcher, metric: MetricCreateSchema, res: aiohttp.ClientResponse | None | float ) -> None: if fetcher.type_ != FetcherType.PING: @@ -134,7 +135,7 @@ def _process_fail( else: _filter = "Service Unavailable" if res is False else "Timeout Error" if res is None else "Unknown Error" alert = AlertCreateSchema(data=metric.model_dump(), filter=_filter) - self.write_alert(alert) + await self.write_alert(alert) self._reschedule_job(fetcher, False) def add_metric(self, metric: MetricCreateSchema): @@ -163,11 +164,11 @@ async def _fetch_it(self, fetcher: Fetcher): except Exception: metric = ApSchedulerService.create_metric(prev, fetcher, res) self.add_metric(metric) - self._process_fail(fetcher, metric, None) + await self._process_fail(fetcher, metric, None) else: metric = ApSchedulerService.create_metric(prev, fetcher, res) self.add_metric(metric) if not metric.ok: - self._process_fail(fetcher, metric, res) + await self._process_fail(fetcher, metric, res) else: self._reschedule_job(fetcher, True) From 14754b6d5975fba1a9793d664f979b0aca0465d9 Mon Sep 17 00:00:00 2001 From: semen603089 Date: Thu, 27 Jul 2023 16:23:39 +0300 Subject: [PATCH 53/53] review --- aciniformes_backend/routes/alert/alert.py | 12 +++++++----- aciniformes_backend/routes/alert/reciever.py | 14 +++++++------- aciniformes_backend/routes/fetcher.py | 12 ++++++------ aciniformes_backend/routes/mectric.py | 6 +++--- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/aciniformes_backend/routes/alert/alert.py b/aciniformes_backend/routes/alert/alert.py index bbdc380..4b7f366 100644 --- a/aciniformes_backend/routes/alert/alert.py +++ b/aciniformes_backend/routes/alert/alert.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from fastapi import APIRouter, Depends @@ -18,12 +20,12 @@ class CreateSchema(BaseModel): class PostResponseSchema(CreateSchema): - id: int | None + id: int | None = None class UpdateSchema(BaseModel): - data: dict[str, str | list | dict] | None - filter: str | None + data: dict[str, str | list | dict] | None = None + filter: str | None = None class GetSchema(BaseModel): @@ -41,8 +43,8 @@ async def create( create_schema: CreateSchema, alert: AlertServiceInterface = Depends(alert_service), ): - id_ = await alert.create(create_schema.dict(exclude_unset=True)) - return PostResponseSchema(**create_schema.dict(), id=id_) + id_ = await alert.create(create_schema.model_dump(exclude_unset=True)) + return PostResponseSchema(**create_schema.model_dump(), id=id_) @router.get("") diff --git a/aciniformes_backend/routes/alert/reciever.py b/aciniformes_backend/routes/alert/reciever.py index 31ff42e..2788590 100644 --- a/aciniformes_backend/routes/alert/reciever.py +++ b/aciniformes_backend/routes/alert/reciever.py @@ -26,21 +26,21 @@ class CreateSchema(BaseModel): class PostResponseSchema(CreateSchema): - url: str | None + url: str | None = None method: Method - receiver_body: dict[str, str | int | list] | None + receiver_body: dict[str, str | int | list] | None = None class UpdateSchema(BaseModel): url: str | None method: Method | None - receiver_body: dict[str, str | int | list] | None + receiver_body: dict[str, str | int | list] | None = None class GetSchema(BaseModel): url: str method: Method - receiver_body: dict[str, str | int | list] + receiver_body: dict[str, str | int | list] | None = None router = APIRouter() @@ -48,8 +48,8 @@ class GetSchema(BaseModel): @router.post("", response_model=PostResponseSchema) async def create(create_schema: CreateSchema, receiver: ReceiverServiceInterface = Depends(receiver_service)): - id_ = await receiver.create(create_schema.dict()) - return PostResponseSchema(**create_schema.dict(), id=id_) + id_ = await receiver.create(create_schema.model_dump()) + return PostResponseSchema(**create_schema.model_dump(), id=id_) @router.get("") @@ -76,7 +76,7 @@ async def update( receiver: ReceiverServiceInterface = Depends(receiver_service), ): try: - res = await receiver.update(id, update_schema.dict(exclude_unset=True)) + res = await receiver.update(id, update_schema.model_dump(exclude_unset=True)) except exc.ObjectNotFound: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) return res diff --git a/aciniformes_backend/routes/fetcher.py b/aciniformes_backend/routes/fetcher.py index ffeac99..a7c02d1 100644 --- a/aciniformes_backend/routes/fetcher.py +++ b/aciniformes_backend/routes/fetcher.py @@ -26,15 +26,15 @@ class CreateSchema(BaseModel): class ResponsePostSchema(CreateSchema): - id: int | None + id: int | None = None class UpdateSchema(BaseModel): - type_: FetcherType | None - address: Annotated[HttpUrl, PlainSerializer(lambda x: str(x), return_type=str)] | None - fetch_data: str | None - delay_ok: int | None - delay_fail: int | None + type_: FetcherType | None = None + address: Annotated[HttpUrl, PlainSerializer(lambda x: str(x), return_type=str)] | None = None + fetch_data: str | None = None + delay_ok: int | None = None + delay_fail: int | None = None class GetSchema(BaseModel): diff --git a/aciniformes_backend/routes/mectric.py b/aciniformes_backend/routes/mectric.py index 84af653..f9edc85 100644 --- a/aciniformes_backend/routes/mectric.py +++ b/aciniformes_backend/routes/mectric.py @@ -15,7 +15,7 @@ class CreateSchema(BaseModel): class ResponsePostSchema(CreateSchema): - id: int | None + id: int | None = None class GetSchema(BaseModel): @@ -33,8 +33,8 @@ async def create( metric_schema: CreateSchema, metric: MetricServiceInterface = Depends(metric_service), ): - id_ = await metric.create(metric_schema.dict()) - return ResponsePostSchema(**metric_schema.dict(), id=id_) + id_ = await metric.create(metric_schema.model_dump()) + return ResponsePostSchema(**metric_schema.model_dump(), id=id_) @router.get("")