done refactoring
Gitea Actions Demo / build_and_push (push) Failing after 1m14s
Details
Gitea Actions Demo / build_and_push (push) Failing after 1m14s
Details
This commit is contained in:
parent
9f5071a7b8
commit
efba462335
|
|
@ -27,6 +27,8 @@ dependencies = [
|
|||
"botocore>=1.42.9",
|
||||
"types-aiofiles>=25.1.0.20251011",
|
||||
"betterconf>=4.5.0",
|
||||
"dataclasses-ujson>=0.0.34",
|
||||
"asyncpg>=0.31.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from functools import lru_cache
|
|||
from betterconf import betterconf, field
|
||||
from betterconf.caster import to_bool, to_int, to_list
|
||||
|
||||
|
||||
@betterconf
|
||||
class AppConfig:
|
||||
# pylint: disable=R0903
|
||||
|
|
@ -18,12 +19,12 @@ class AppConfig:
|
|||
app_port: int = field("APP_PORT", default=8000, caster=to_int)
|
||||
app_public_url: str = field("APP_PUBLIC_URL", default="http://127.0.0.1:8000")
|
||||
|
||||
|
||||
sentry_dns: str = field("SENTRY_DNS", default="")
|
||||
log_level: str = field("LOG_LEVEL", "INFO")
|
||||
|
||||
|
||||
db_uri: str = field("DB_URI", "postgresql+asyncpg://svcuser:svcpass@localhost:5432/svc")
|
||||
db_uri: str = field(
|
||||
"DB_URI", "postgresql+asyncpg://svcuser:svcpass@localhost:5432/svc"
|
||||
)
|
||||
db_pass_salt: str = field("DB_PASS_SALT", "")
|
||||
db_search_path: str = field("DB_SEARCH_PATH", "public")
|
||||
|
||||
|
|
@ -34,7 +35,6 @@ class AppConfig:
|
|||
fs_s3_endpoint: str = field("FS_S3_ENDPOINT", "")
|
||||
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_app_config() -> AppConfig:
|
||||
# pylint: disable=C0116
|
||||
|
|
|
|||
|
|
@ -2,4 +2,11 @@ from server.infra.db.abc import AbstractDB, AbstractSession, ExecuteFun
|
|||
from server.infra.db.mock import MockDB, MockSession
|
||||
from server.infra.db.pg import AsyncDB
|
||||
|
||||
__all__ = ["AsyncDB", "AbstractDB", "ExecuteFun", "AbstractSession", "MockDB", "MockSession"]
|
||||
__all__ = [
|
||||
"AsyncDB",
|
||||
"AbstractDB",
|
||||
"ExecuteFun",
|
||||
"AbstractSession",
|
||||
"MockDB",
|
||||
"MockSession",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ class AsyncDB(AbstractDB):
|
|||
if "postgresql+asyncpg" in str(cnf.db_uri):
|
||||
con_arg = {"server_settings": {"search_path": cnf.db_search_path}}
|
||||
self.engine = asyncio.create_async_engine(
|
||||
str(cnf.db_uri), echo=bool(cnf.app_debug), connect_args=con_arg, pool_recycle=1800
|
||||
str(cnf.db_uri),
|
||||
echo=bool(cnf.app_debug),
|
||||
connect_args=con_arg,
|
||||
pool_recycle=1800,
|
||||
)
|
||||
|
||||
# self.engine.execution_options(stream_results=True)
|
||||
|
|
|
|||
|
|
@ -52,9 +52,21 @@ LOGGING_CONFIG = {
|
|||
},
|
||||
"loggers": {
|
||||
"": {"handlers": ["default"], "level": cnf.log_level, "propagate": False},
|
||||
"uvicorn.access": {"handlers": ["uvicorn_access"], "level": "INFO", "propagate": False},
|
||||
"uvicorn.error": {"handlers": ["uvicorn_default"], "level": "INFO", "propagate": False},
|
||||
"uvicorn.asgi": {"handlers": ["uvicorn_default"], "level": "INFO", "propagate": False},
|
||||
"uvicorn.access": {
|
||||
"handlers": ["uvicorn_access"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"uvicorn.error": {
|
||||
"handlers": ["uvicorn_default"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"uvicorn.asgi": {
|
||||
"handlers": ["uvicorn_default"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,11 +94,19 @@ class FilterQuery:
|
|||
|
||||
@staticmethod
|
||||
def mass_and(fields: list[object], values: list[Any]) -> "FilterQuery":
|
||||
return FilterQuery(filters=[Filter.eq(field, val) for field, val in zip(fields, values)])
|
||||
return FilterQuery(
|
||||
filters=[Filter.eq(field, val) for field, val in zip(fields, values)]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def mass_or(fields: list[object], values: list[Any]) -> "FilterQuery":
|
||||
return FilterQuery(filters=[Filter.or_([Filter.eq(field, val) for field, val in zip(fields, values)])])
|
||||
return FilterQuery(
|
||||
filters=[
|
||||
Filter.or_(
|
||||
[Filter.eq(field, val) for field, val in zip(fields, values)]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def eq(field: object, value: Any) -> "FilterQuery":
|
||||
|
|
@ -129,7 +137,9 @@ class DataclassInstance(Protocol):
|
|||
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
|
||||
|
||||
|
||||
async def indexes_by_id(input_data: list, values: list[str], id_name="id") -> Optional[list[int]]:
|
||||
async def indexes_by_id(
|
||||
input_data: list, values: list[str], id_name="id"
|
||||
) -> Optional[list[int]]:
|
||||
r_data: list[int] = []
|
||||
for i, _ in enumerate(input_data):
|
||||
if getattr(input_data[i], id_name) in values:
|
||||
|
|
@ -139,7 +149,9 @@ async def indexes_by_id(input_data: list, values: list[str], id_name="id") -> Op
|
|||
return r_data
|
||||
|
||||
|
||||
def data_by_filter[T: DataclassInstance](input_data: list[T], q: FilterQuery) -> list[T]:
|
||||
def data_by_filter[T: DataclassInstance](
|
||||
input_data: list[T], q: FilterQuery
|
||||
) -> list[T]:
|
||||
# can't do query AND(OR() + AND())
|
||||
data: list[T] = []
|
||||
data_or: list[T] = []
|
||||
|
|
@ -233,10 +245,14 @@ def sqlalchemy_conditions(q: FilterQuery):
|
|||
conditions = []
|
||||
for f in q.filters:
|
||||
if f.sign == FilterSign.OR:
|
||||
conditions.append(or_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right))))
|
||||
conditions.append(
|
||||
or_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right)))
|
||||
)
|
||||
continue
|
||||
if f.sign == FilterSign.AND:
|
||||
conditions.append(and_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right))))
|
||||
conditions.append(
|
||||
and_(*sqlalchemy_conditions(q=FilterQuery(filters=f.right)))
|
||||
)
|
||||
continue
|
||||
if f.left is None:
|
||||
continue
|
||||
|
|
@ -266,7 +282,9 @@ def sqlalchemy_conditions(q: FilterQuery):
|
|||
return conditions
|
||||
|
||||
|
||||
def sqlalchemy_restrictions(f: FilterQuery, q: Select, dict_to_sort: Optional[dict] = None) -> Select:
|
||||
def sqlalchemy_restrictions(
|
||||
f: FilterQuery, q: Select, dict_to_sort: Optional[dict] = None
|
||||
) -> Select:
|
||||
if f.limit:
|
||||
q = q.limit(f.limit)
|
||||
if f.offset:
|
||||
|
|
|
|||
|
|
@ -1,31 +1,15 @@
|
|||
import asyncio
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
import inject
|
||||
import markdown
|
||||
from litestar import (
|
||||
Controller,
|
||||
get,
|
||||
post,
|
||||
MediaType,
|
||||
Litestar,
|
||||
)
|
||||
from litestar.enums import RequestEncodingType
|
||||
from litestar.datastructures import UploadFile
|
||||
from litestar.params import Body
|
||||
from litestar.exceptions import HTTPException
|
||||
from litestar.contrib.jinja import JinjaTemplateEngine
|
||||
from litestar.template.config import TemplateConfig
|
||||
from litestar.response import Template
|
||||
from litestar.static_files import create_static_files_router
|
||||
|
||||
from server.infra import logger
|
||||
from server.config import get_app_config
|
||||
from server.infra.cache import LocalCacheRepository
|
||||
from server.infra.db import AsyncDB
|
||||
from server.modules.descriptions import CharactersService, Breed, CharactersRepository
|
||||
from server.modules.recognizer import RecognizerService, RecognizerRepository
|
||||
from server.modules.descriptions import CharactersService
|
||||
from server.modules.recognizer import RecognizerService
|
||||
|
||||
|
||||
class DescriptionController(Controller):
|
||||
path = "/"
|
||||
|
|
@ -48,6 +32,7 @@ class DescriptionController(Controller):
|
|||
|
||||
@get("/dogs-characteristics")
|
||||
async def dogs_characteristics(self) -> Template:
|
||||
characters_service: CharactersService = inject.instance(CharactersService)
|
||||
breeds = await characters_service.get_characters()
|
||||
return Template(
|
||||
template_name="dogs-characteristics.html", context={"breeds": breeds}
|
||||
|
|
@ -55,9 +40,11 @@ class DescriptionController(Controller):
|
|||
|
||||
@get("/dogs-characteristics/{name:str}")
|
||||
async def beer_description(self, name: str) -> Template:
|
||||
characters_service: CharactersService = inject.instance(CharactersService)
|
||||
breed = await characters_service.get_character(name)
|
||||
if breed is None:
|
||||
raise HTTPException(status_code=404, detail="Порода не найдена")
|
||||
recognizer_service: RecognizerService = inject.instance(RecognizerService)
|
||||
images = await recognizer_service.images_dogs()
|
||||
return Template(
|
||||
template_name="beers-description.html",
|
||||
|
|
@ -67,4 +54,3 @@ class DescriptionController(Controller):
|
|||
"images": [f"/static/assets/dog/{name}/{i}" for i in images[name]],
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -1,31 +1,14 @@
|
|||
import asyncio
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
import inject
|
||||
import markdown
|
||||
from litestar import (
|
||||
Controller,
|
||||
get,
|
||||
post,
|
||||
MediaType,
|
||||
Litestar,
|
||||
)
|
||||
from litestar.enums import RequestEncodingType
|
||||
from litestar.datastructures import UploadFile
|
||||
from litestar.params import Body
|
||||
from litestar.exceptions import HTTPException
|
||||
from litestar.contrib.jinja import JinjaTemplateEngine
|
||||
from litestar.template.config import TemplateConfig
|
||||
from litestar.response import Template
|
||||
from litestar.static_files import create_static_files_router
|
||||
|
||||
from server.infra import logger
|
||||
from server.config import get_app_config
|
||||
from server.infra.cache import LocalCacheRepository
|
||||
from server.infra.db import AsyncDB
|
||||
from server.modules.descriptions import CharactersService, Breed, CharactersRepository
|
||||
from server.modules.recognizer import RecognizerService, RecognizerRepository
|
||||
from server.modules.recognizer import RecognizerService
|
||||
|
||||
|
||||
class BreedsController(Controller):
|
||||
path = "/beerds"
|
||||
|
|
@ -34,6 +17,7 @@ class BreedsController(Controller):
|
|||
async def beerds_dogs(
|
||||
self, data: UploadFile = Body(media_type=RequestEncodingType.MULTI_PART)
|
||||
) -> dict:
|
||||
recognizer_service: RecognizerService = inject.instance(RecognizerService)
|
||||
body = await data.read()
|
||||
return await recognizer_service.predict_dog_image(body)
|
||||
|
||||
|
|
@ -41,5 +25,6 @@ class BreedsController(Controller):
|
|||
async def beerds_cats(
|
||||
self, data: UploadFile = Body(media_type=RequestEncodingType.MULTI_PART)
|
||||
) -> dict:
|
||||
recognizer_service: RecognizerService = inject.instance(RecognizerService)
|
||||
body = await data.read()
|
||||
return await recognizer_service.predict_cat_image(body)
|
||||
|
|
|
|||
|
|
@ -1,35 +1,17 @@
|
|||
import asyncio
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
import inject
|
||||
import markdown
|
||||
from litestar import (
|
||||
Controller,
|
||||
get,
|
||||
post,
|
||||
MediaType,
|
||||
Litestar,
|
||||
)
|
||||
from litestar.enums import RequestEncodingType
|
||||
from litestar.datastructures import UploadFile
|
||||
from litestar.params import Body
|
||||
from litestar.exceptions import HTTPException
|
||||
from litestar.contrib.jinja import JinjaTemplateEngine
|
||||
from litestar.template.config import TemplateConfig
|
||||
from litestar.response import Template
|
||||
from litestar.static_files import create_static_files_router
|
||||
|
||||
from server.infra import logger
|
||||
from server.config import get_app_config
|
||||
from server.infra.cache import LocalCacheRepository
|
||||
from server.infra.db import AsyncDB
|
||||
from server.modules.descriptions import CharactersService, Breed, CharactersRepository
|
||||
from server.modules.recognizer import RecognizerService, RecognizerRepository
|
||||
from server.modules.descriptions import CharactersService, Breed
|
||||
|
||||
|
||||
class SeoController(Controller):
|
||||
@get("/sitemap.xml", media_type=MediaType.XML)
|
||||
async def sitemaps(self) -> bytes:
|
||||
characters_service: CharactersService = inject.instance(CharactersService)
|
||||
breeds: list[Breed] = await characters_service.get_characters()
|
||||
lastmod = "2025-10-04T19:01:03+00:00"
|
||||
beers_url = ""
|
||||
|
|
|
|||
|
|
@ -3,51 +3,35 @@ from pathlib import Path
|
|||
import os
|
||||
|
||||
import inject
|
||||
import markdown
|
||||
from litestar import (
|
||||
Controller,
|
||||
get,
|
||||
post,
|
||||
MediaType,
|
||||
Litestar,
|
||||
)
|
||||
from litestar.enums import RequestEncodingType
|
||||
from litestar.datastructures import UploadFile
|
||||
from litestar.params import Body
|
||||
from litestar.exceptions import HTTPException
|
||||
from litestar.contrib.jinja import JinjaTemplateEngine
|
||||
from litestar.template.config import TemplateConfig
|
||||
from litestar.response import Template
|
||||
from litestar.static_files import create_static_files_router
|
||||
|
||||
from server.infra import logger
|
||||
from server.config import get_app_config
|
||||
from server.infra.web import BreedsController, DescriptionController, SeoController
|
||||
from server.infra.db import AsyncDB
|
||||
from server.modules.descriptions import CharactersService, Breed, CharactersRepository
|
||||
from server.modules.descriptions import CharactersService, CharactersRepository
|
||||
from server.modules.recognizer import RecognizerService, RecognizerRepository
|
||||
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
|
||||
|
||||
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
|
||||
|
||||
def inject_config(binder: inject.Binder):
|
||||
"""initialization inject_config for server FastApi"""
|
||||
|
||||
loop.run_until_complete(db.connect())
|
||||
cnf = get_app_config()
|
||||
db = AsyncDB(cnf)
|
||||
loop.run_until_complete(db.connect())
|
||||
binder.bind(RecognizerService, RecognizerService(RecognizerRepository()))
|
||||
binder.bind(CharactersService, CharactersService(CharactersRepository()))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
inject.configure(inject_config)
|
||||
app = Litestar(
|
||||
debug=True,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from datetime import datetime
|
||||
|
||||
from dataclasses_ujson.dataclasses_ujson import UJsonMixin # type: ignore
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Attachment:
|
||||
class Attachment(UJsonMixin):
|
||||
id: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ from server.modules.attachments.domains.attachments import Attachment
|
|||
|
||||
class AttachmentRepository(metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
async def get_by_id(self, session: AbstractSession, attach_id: list[str]) -> list[Attachment]:
|
||||
async def get_by_id(
|
||||
self, session: AbstractSession, attach_id: list[str]
|
||||
) -> list[Attachment]:
|
||||
"""Get Attachment by ID"""
|
||||
pass
|
||||
|
||||
|
|
@ -87,7 +89,9 @@ class MockAttachmentRepository(AttachmentRepository):
|
|||
}
|
||||
self._db = MockDB(get_app_config())
|
||||
|
||||
async def get_by_id(self, session: AbstractSession, attach_id: list[str]) -> list[Attachment]:
|
||||
async def get_by_id(
|
||||
self, session: AbstractSession, attach_id: list[str]
|
||||
) -> list[Attachment]:
|
||||
f: list[Attachment] = []
|
||||
for f_id in attach_id:
|
||||
f_item = self._data.get(f_id)
|
||||
|
|
@ -115,7 +119,9 @@ class DBAttachmentRepository(AttachmentRepository):
|
|||
def __init__(self, db: AsyncDB):
|
||||
self._db = db
|
||||
|
||||
async def get_by_id(self, session: AbstractSession, attach_id: list[str]) -> list[Attachment]:
|
||||
async def get_by_id(
|
||||
self, session: AbstractSession, attach_id: list[str]
|
||||
) -> list[Attachment]:
|
||||
q = select(Attachment).where(
|
||||
Attachment.id.in_(attach_id) # type: ignore
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import hashlib
|
||||
import hmac
|
||||
import os.path
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from datetime import UTC, datetime
|
||||
from enum import Enum
|
||||
from http import HTTPStatus
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, AsyncIterable, AsyncIterator, Optional
|
||||
from urllib.parse import urlparse
|
||||
import uuid
|
||||
|
||||
import aioboto3 # type: ignore
|
||||
|
|
@ -141,7 +138,9 @@ class S3StorageDriver(StorageDriver):
|
|||
return self._session.client("s3", endpoint_url=self._cnf.fs_s3_endpoint)
|
||||
|
||||
def _normalize_path(self, path: str) -> str:
|
||||
return f"{S3StorageDriver._prefix}{path}".replace(self._cnf.fs_local_mount_dir, "")
|
||||
return f"{S3StorageDriver._prefix}{path}".replace(
|
||||
self._cnf.fs_local_mount_dir, ""
|
||||
)
|
||||
|
||||
async def put(self, data: bytes) -> str:
|
||||
sign = hashlib.file_digest(BytesIO(data), "sha256").hexdigest()
|
||||
|
|
@ -177,7 +176,9 @@ class S3StorageDriver(StorageDriver):
|
|||
self._logger.error(f"stream client error: {str(e)}, path: {path}")
|
||||
raise FileNotFoundError
|
||||
except Exception as e:
|
||||
self._logger.error(f"stream error: {type(e).__name__} {str(e)}, path: {path}")
|
||||
self._logger.error(
|
||||
f"stream error: {type(e).__name__} {str(e)}, path: {path}"
|
||||
)
|
||||
raise FileNotFoundError
|
||||
|
||||
async def take(self, path: str) -> Optional[bytes]:
|
||||
|
|
@ -190,7 +191,10 @@ class S3StorageDriver(StorageDriver):
|
|||
|
||||
async def delete(self, path: str) -> None:
|
||||
async with await self._client() as s3:
|
||||
await s3.delete_object(Bucket=self._cnf.fs_s3_bucket, Key=self._normalize_path(path))
|
||||
await s3.delete_object(
|
||||
Bucket=self._cnf.fs_s3_bucket, Key=self._normalize_path(path)
|
||||
)
|
||||
|
||||
|
||||
RESIZE_MAX_SIZE = 100_000
|
||||
RESIZE_PARAMS = (500, 500)
|
||||
|
|
@ -240,22 +244,8 @@ class AtachmentService:
|
|||
|
||||
def url(self, attachment_id: str, content_type: str | None = None) -> str:
|
||||
return f"{self._cnf.app_public_url}/api/v0/attachment/{attachment_id}.original.{
|
||||
self.extension(content_type)}"
|
||||
|
||||
def audio(self, attachment: list[Attachment] | None) -> list[Attachment]:
|
||||
if not attachment:
|
||||
return []
|
||||
return [a for a in attachment if a.media_type == MediaType.AUDIO.value and a.is_deleted == False]
|
||||
|
||||
def svg_images(self, attachment: list[Attachment] | None) -> list[Attachment]:
|
||||
if not attachment:
|
||||
return []
|
||||
return [a for a in attachment if a.media_type == MediaType.SVG_IMAGE.value and a.is_deleted == False]
|
||||
|
||||
def images(self, attachment: list[Attachment] | None) -> list[Attachment]:
|
||||
if not attachment:
|
||||
return []
|
||||
return [a for a in attachment if a.media_type == MediaType.IMAGE.value and a.is_deleted == False]
|
||||
self.extension(content_type)
|
||||
}"
|
||||
|
||||
async def create(self, file: bytes, user_id: str) -> Attachment:
|
||||
path = await self._driver.put(file)
|
||||
|
|
@ -269,12 +259,14 @@ class AtachmentService:
|
|||
created_by=user_id,
|
||||
id=str(uuid.uuid4()),
|
||||
created_at=datetime.now(UTC),
|
||||
updated_at=datetime.now(UTC)
|
||||
updated_at=datetime.now(UTC),
|
||||
)
|
||||
await self._repository.create(attach)
|
||||
return attach
|
||||
|
||||
async def get_info(self, session: AbstractSession | None, attach_id: list[str]) -> list[Attachment]:
|
||||
async def get_info(
|
||||
self, session: AbstractSession | None, attach_id: list[str]
|
||||
) -> list[Attachment]:
|
||||
if not attach_id:
|
||||
return []
|
||||
if session is not None:
|
||||
|
|
@ -285,13 +277,17 @@ class AtachmentService:
|
|||
def get_name(self, attachment: Attachment) -> str:
|
||||
return f"{attachment.id}.{self.extension(attachment.content_type)}"
|
||||
|
||||
async def get_data(self, session: AbstractSession, attach_id: str) -> Optional[bytes]:
|
||||
async def get_data(
|
||||
self, session: AbstractSession, attach_id: str
|
||||
) -> Optional[bytes]:
|
||||
file = await self._repository.get_by_id(session, [attach_id])
|
||||
if not file:
|
||||
return None
|
||||
return await self._driver.take(file[0].path)
|
||||
|
||||
async def get_stream(self, session: AbstractSession | None, attach_id: str) -> AsyncIterator[bytes]:
|
||||
async def get_stream(
|
||||
self, session: AbstractSession | None, attach_id: str
|
||||
) -> AsyncIterator[bytes]:
|
||||
async def _stream_iterator(is_empty: bool):
|
||||
if is_empty:
|
||||
return
|
||||
|
|
@ -347,5 +343,7 @@ class AtachmentService:
|
|||
f"delete:{item.path}",
|
||||
)
|
||||
path = await self._driver.put(d)
|
||||
await self._repository.update(item.id, path=path, content_type="image/jpeg", size=len(d))
|
||||
await self._repository.update(
|
||||
item.id, path=path, content_type="image/jpeg", size=len(d)
|
||||
)
|
||||
await self._driver.delete(item.path)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class CharactersRepository(ACharactersRepository):
|
|||
|
||||
@cached(ttl=60, cache=Cache.MEMORY)
|
||||
async def get_characters(self) -> list[Breed]:
|
||||
breed_dir = Path("server/services/descriptions/repository/breed_descriptions")
|
||||
breed_dir = Path("server/modules/descriptions/repository/breed_descriptions")
|
||||
breeds: list[Breed] = []
|
||||
|
||||
# Идем по каждому текстовому файлу с описанием породы
|
||||
|
|
@ -57,7 +57,7 @@ class PGCharactersRepository(ACharactersRepository):
|
|||
|
||||
@cached(ttl=60, cache=Cache.MEMORY)
|
||||
async def get_characters(self) -> list[Breed]:
|
||||
breed_dir = Path("server/services/descriptions/repository/breed_descriptions")
|
||||
breed_dir = Path("server/modules/descriptions/repository/breed_descriptions")
|
||||
breeds: list[Breed] = []
|
||||
|
||||
# Идем по каждому текстовому файлу с описанием породы
|
||||
|
|
|
|||
|
|
@ -29,18 +29,18 @@ class RecognizerRepository(ARecognizerRepository):
|
|||
|
||||
@cached(ttl=60, cache=Cache.MEMORY)
|
||||
async def images_dogs(self) -> dict:
|
||||
with open("server/services/recognizer/repository/meta/images.json", "r") as f:
|
||||
with open("server/modules/recognizer/repository/meta/images.json", "r") as f:
|
||||
return ujson.loads(f.read())["dog"]
|
||||
|
||||
@cached(ttl=60, cache=Cache.MEMORY)
|
||||
async def images_cats(self) -> dict:
|
||||
with open("server/services/recognizer/repository/meta/images.json", "r") as f:
|
||||
with open("server/modules/recognizer/repository/meta/images.json", "r") as f:
|
||||
return ujson.loads(f.read())["cat"]
|
||||
|
||||
@lru_cache
|
||||
def labels_cats(self) -> dict:
|
||||
with open(
|
||||
"server/services/recognizer/repository/meta/labels_cats.json", "r"
|
||||
"server/modules/recognizer/repository/meta/labels_cats.json", "r"
|
||||
) as f:
|
||||
data_labels = f.read()
|
||||
return ujson.loads(data_labels)
|
||||
|
|
@ -48,7 +48,7 @@ class RecognizerRepository(ARecognizerRepository):
|
|||
@lru_cache
|
||||
def labels_dogs(self) -> dict:
|
||||
with open(
|
||||
"server/services/recognizer/repository/meta/labels_dogs.json", "r"
|
||||
"server/modules/recognizer/repository/meta/labels_dogs.json", "r"
|
||||
) as f:
|
||||
data_labels = f.read()
|
||||
return ujson.loads(data_labels)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class RecognizerService:
|
|||
predicted_data = self._predict(image, DOG_MODEL)
|
||||
results = {}
|
||||
images = []
|
||||
description = {}
|
||||
description: dict[str, list] = {}
|
||||
images_dogs = await self._repository.images_dogs()
|
||||
for d in predicted_data:
|
||||
predicted_idx, probabilities = d
|
||||
|
|
@ -55,7 +55,9 @@ class RecognizerService:
|
|||
],
|
||||
}
|
||||
)
|
||||
description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(" ", "_")}")
|
||||
description.setdefault(name, []).append(
|
||||
f"/dogs-characteristics/{name.replace(' ', '_')}"
|
||||
)
|
||||
results[probabilities] = name
|
||||
return {
|
||||
"results": results,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ async function SavePhoto(self) {
|
|||
// Обработка основных результатов
|
||||
for (let key in json.results) {
|
||||
if (json.description != undefined) {
|
||||
text += `<div class='image-block'><div class='image-text'>${json.results[key]} (вероятность: ${Math.round(parseFloat(key) * 100)}%) <br/><a href="${json.description[json.results[key]]}" target='_blank'>Описание </a></div>`;
|
||||
text += `<div class='image-block'><div class='image-text'>${json.results[key]} (вероятность: ${Math.round(parseFloat(key) * 100)}%) <br/><a href="/dogs-characteristics/${json.results[key]}" target='_blank'>Описание </a></div>`;
|
||||
} else {
|
||||
text += `<div class='image-block'><div class='image-text'>${json.results[key]} (вероятность: ${Math.round(parseFloat(key) * 100)}%)</div>`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,6 @@
|
|||
{% block form %}{% endblock %}
|
||||
</body>
|
||||
</section>
|
||||
<script src="/static/scripts.js?v=6"></script>
|
||||
<script src="/static/scripts.js?v=7"></script>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
55
uv.lock
55
uv.lock
|
|
@ -14,8 +14,10 @@ source = { virtual = "." }
|
|||
dependencies = [
|
||||
{ name = "aiocache" },
|
||||
{ name = "aiofiles" },
|
||||
{ name = "asyncpg" },
|
||||
{ name = "betterconf" },
|
||||
{ name = "botocore" },
|
||||
{ name = "dataclasses-ujson" },
|
||||
{ name = "granian" },
|
||||
{ name = "inject" },
|
||||
{ name = "jinja2" },
|
||||
|
|
@ -56,8 +58,10 @@ default = [
|
|||
requires-dist = [
|
||||
{ name = "aiocache" },
|
||||
{ name = "aiofiles", specifier = ">=25.1.0" },
|
||||
{ name = "asyncpg", specifier = ">=0.31.0" },
|
||||
{ name = "betterconf", specifier = ">=4.5.0" },
|
||||
{ name = "botocore", specifier = ">=1.42.9" },
|
||||
{ name = "dataclasses-ujson", specifier = ">=0.0.34" },
|
||||
{ name = "granian", specifier = "==2.5" },
|
||||
{ name = "inject", specifier = ">=5.3.0" },
|
||||
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||
|
|
@ -127,6 +131,38 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/19/24/44299477fe7dcc9cb58d0a57d5a7588d6af2ff403fdd2d47a246c91a3246/anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5", size = 80896 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asyncpg"
|
||||
version = "0.31.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067 },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744 },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "betterconf"
|
||||
version = "4.5.0"
|
||||
|
|
@ -285,6 +321,16 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dataclasses-ujson"
|
||||
version = "0.0.34"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "types-ujson" },
|
||||
{ name = "ujson" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/73/c8aa3fa926f3368f65ec9bb7a74aa42349ed9aa43c54391102a5f0e7ab5c/dataclasses_ujson-0.0.34.tar.gz", hash = "sha256:14b70f85ef57f55e46e0a5233b5d70dd2d40d7e3aa202cfe105c03dd23ed109c", size = 4763 }
|
||||
|
||||
[[package]]
|
||||
name = "faker"
|
||||
version = "38.0.0"
|
||||
|
|
@ -1460,6 +1506,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-ujson"
|
||||
version = "5.10.0.20250822"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/bd/d372d44534f84864a96c19a7059d9b4d29db8541828b8b9dc3040f7a46d0/types_ujson-5.10.0.20250822.tar.gz", hash = "sha256:0a795558e1f78532373cf3f03f35b1f08bc60d52d924187b97995ee3597ba006", size = 8437 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue