search-fix, devstart-fix, cache-fix, logs-less
Some checks failed
Deploy on push / deploy (push) Failing after 5s

This commit is contained in:
2025-07-31 19:12:21 +03:00
parent e7230ba63c
commit 809bda2b56
6 changed files with 60 additions and 21 deletions

View File

@@ -2,6 +2,11 @@
Все значимые изменения в проекте документируются в этом файле. Все значимые изменения в проекте документируются в этом файле.
## [0.9.1] - 2025-07-31
- исправлен `dev.py`
- исправлен запуск поиска
- незначительные улучшения логов
## [0.9.0] - 2025-07-31 ## [0.9.0] - 2025-07-31
## Миграция на типы SQLAlchemy2 ## Миграция на типы SQLAlchemy2

21
cache/cache.py vendored
View File

@@ -37,7 +37,6 @@ from sqlalchemy import and_, join, select
from auth.orm import Author, AuthorFollower from auth.orm import Author, AuthorFollower
from orm.shout import Shout, ShoutAuthor, ShoutTopic from orm.shout import Shout, ShoutAuthor, ShoutTopic
from orm.topic import Topic, TopicFollower from orm.topic import Topic, TopicFollower
from resolvers.stat import get_with_stat
from services.db import local_session from services.db import local_session
from services.redis import redis from services.redis import redis
from utils.encoders import fast_json_dumps from utils.encoders import fast_json_dumps
@@ -119,7 +118,7 @@ async def update_follower_stat(follower_id: int, entity_type: str, count: int) -
# Get author from cache # Get author from cache
async def get_cached_author(author_id: int, get_with_stat) -> dict | None: async def get_cached_author(author_id: int, get_with_stat=None) -> dict | None:
logger.debug(f"[get_cached_author] Начало выполнения для author_id: {author_id}") logger.debug(f"[get_cached_author] Начало выполнения для author_id: {author_id}")
author_key = f"author:id:{author_id}" author_key = f"author:id:{author_id}"
@@ -137,6 +136,9 @@ async def get_cached_author(author_id: int, get_with_stat) -> dict | None:
logger.debug("[get_cached_author] Данные не найдены в кэше, загрузка из БД") logger.debug("[get_cached_author] Данные не найдены в кэше, загрузка из БД")
# Load from database if not found in cache # Load from database if not found in cache
if get_with_stat is None:
from resolvers.stat import get_with_stat
q = select(Author).where(Author.id == author_id) q = select(Author).where(Author.id == author_id)
authors = get_with_stat(q) authors = get_with_stat(q)
logger.debug(f"[get_cached_author] Результат запроса из БД: {len(authors) if authors else 0} записей") logger.debug(f"[get_cached_author] Результат запроса из БД: {len(authors) if authors else 0} записей")
@@ -188,12 +190,15 @@ async def get_cached_topic(topic_id: int) -> dict | None:
# Get topic by slug from cache # Get topic by slug from cache
async def get_cached_topic_by_slug(slug: str, get_with_stat) -> dict | None: async def get_cached_topic_by_slug(slug: str, get_with_stat=None) -> dict | None:
topic_key = f"topic:slug:{slug}" topic_key = f"topic:slug:{slug}"
result = await redis.execute("GET", topic_key) result = await redis.execute("GET", topic_key)
if result: if result:
return orjson.loads(result) return orjson.loads(result)
# Load from database if not found in cache # Load from database if not found in cache
if get_with_stat is None:
from resolvers.stat import get_with_stat
topic_query = select(Topic).where(Topic.slug == slug) topic_query = select(Topic).where(Topic.slug == slug)
topics = get_with_stat(topic_query) topics = get_with_stat(topic_query)
if topics: if topics:
@@ -337,7 +342,7 @@ async def get_cached_follower_topics(author_id: int):
# Get author by author_id from cache # Get author by author_id from cache
async def get_cached_author_by_id(author_id: int, get_with_stat): async def get_cached_author_by_id(author_id: int, get_with_stat=None):
""" """
Retrieve author information by author_id, checking the cache first, then the database. Retrieve author information by author_id, checking the cache first, then the database.
@@ -354,6 +359,9 @@ async def get_cached_author_by_id(author_id: int, get_with_stat):
return orjson.loads(cached_author_data) return orjson.loads(cached_author_data)
# If data is not found in cache, query the database # If data is not found in cache, query the database
if get_with_stat is None:
from resolvers.stat import get_with_stat
author_query = select(Author).where(Author.id == author_id) author_query = select(Author).where(Author.id == author_id)
authors = get_with_stat(author_query) authors = get_with_stat(author_query)
if authors: if authors:
@@ -521,7 +529,7 @@ async def get_cached_entity(entity_type: str, entity_id: int, get_method, cache_
return None return None
async def cache_by_id(entity, entity_id: int, cache_method): async def cache_by_id(entity, entity_id: int, cache_method, get_with_stat=None):
""" """
Кэширует сущность по ID, используя указанный метод кэширования Кэширует сущность по ID, используя указанный метод кэширования
@@ -531,6 +539,9 @@ async def cache_by_id(entity, entity_id: int, cache_method):
cache_method: функция кэширования cache_method: функция кэширования
""" """
if get_with_stat is None:
from resolvers.stat import get_with_stat
caching_query = select(entity).where(entity.id == entity_id) caching_query = select(entity).where(entity.id == entity_id)
result = get_with_stat(caching_query) result = get_with_stat(caching_query)
if not result or not result[0]: if not result or not result[0]:

4
dev.py
View File

@@ -76,7 +76,7 @@ def generate_certificates(domain="localhost", cert_file="localhost.pem", key_fil
return None, None return None, None
def run_server(host="localhost", port=8000, use_https=False, workers=1, domain="localhost") -> None: def run_server(host="127.0.0.1", port=8000, use_https=False, workers=1, domain="localhost") -> None:
""" """
Запускает сервер Granian с поддержкой HTTPS при необходимости Запускает сервер Granian с поддержкой HTTPS при необходимости
@@ -136,7 +136,7 @@ if __name__ == "__main__":
parser.add_argument("--workers", type=int, default=1, help="Количество рабочих процессов") parser.add_argument("--workers", type=int, default=1, help="Количество рабочих процессов")
parser.add_argument("--domain", type=str, default="localhost", help="Домен для сертификата") parser.add_argument("--domain", type=str, default="localhost", help="Домен для сертификата")
parser.add_argument("--port", type=int, default=8000, help="Порт для запуска сервера") parser.add_argument("--port", type=int, default=8000, help="Порт для запуска сервера")
parser.add_argument("--host", type=str, default="localhost", help="Хост для запуска сервера") parser.add_argument("--host", type=str, default="127.0.0.1", help="Хост для запуска сервера")
args = parser.parse_args() args = parser.parse_args()

View File

@@ -36,7 +36,7 @@ class BaseModel(DeclarativeBase):
""" """
column_names = filter(lambda x: x not in FILTERED_FIELDS, self.__table__.columns.keys()) column_names = filter(lambda x: x not in FILTERED_FIELDS, self.__table__.columns.keys())
data: builtins.dict[str, Any] = {} data: builtins.dict[str, Any] = {}
logger.debug(f"Converting object to dictionary {'with access' if access else 'without access'}") # logger.debug(f"Converting object to dictionary {'with access' if access else 'without access'}")
try: try:
for column_name in column_names: for column_name in column_names:
try: try:

View File

@@ -126,23 +126,34 @@ def get_json_builder() -> tuple[Any, Any, Any]:
json_builder, json_array_builder, json_cast = get_json_builder() json_builder, json_array_builder, json_cast = get_json_builder()
def create_table_if_not_exists(connection_or_engine: Connection | Engine, model_cls: Type[DeclarativeBase]) -> None: def create_table_if_not_exists(
connection_or_engine_or_session: Connection | Engine | Session, model_cls: Type[DeclarativeBase]
) -> None:
"""Creates table for the given model if it doesn't exist""" """Creates table for the given model if it doesn't exist"""
# If an Engine is passed, get a connection from it
connection = connection_or_engine.connect() if isinstance(connection_or_engine, Engine) else connection_or_engine # Handle different input types
if isinstance(connection_or_engine_or_session, Session):
# Use session's bind
connection = connection_or_engine_or_session.get_bind()
should_close = False
elif isinstance(connection_or_engine_or_session, Engine):
# Get a connection from engine
connection = connection_or_engine_or_session.connect()
should_close = True
else:
# Already a connection
connection = connection_or_engine_or_session
should_close = False
try: try:
inspector = inspect(connection) inspector = inspect(connection)
if not inspector.has_table(model_cls.__tablename__): if not inspector.has_table(model_cls.__tablename__):
# Use SQLAlchemy's built-in table creation instead of manual SQL generation # Use SQLAlchemy's built-in table creation instead of manual SQL generation
from sqlalchemy.schema import CreateTable model_cls.__table__.create(bind=connection, checkfirst=False)
create_stmt = CreateTable(model_cls.__table__) # type: ignore[arg-type]
connection.execute(create_stmt)
logger.info(f"Created table: {model_cls.__tablename__}") logger.info(f"Created table: {model_cls.__tablename__}")
finally: finally:
# If we created a connection from an Engine, close it # Close connection only if we created it
if isinstance(connection_or_engine, Engine): if should_close:
connection.close() connection.close()

View File

@@ -189,10 +189,22 @@ class SearchCache:
class SearchService: class SearchService:
def __init__(self) -> None: def __init__(self) -> None:
logger.info(f"Initializing search service with URL: {TXTAI_SERVICE_URL}") logger.info(f"Initializing search service with URL: {TXTAI_SERVICE_URL}")
self.available = SEARCH_ENABLED
# Проверяем валидность URL
if not TXTAI_SERVICE_URL or not TXTAI_SERVICE_URL.startswith(("http://", "https://")):
self.available = False
logger.info("Search disabled (invalid TXTAI_SERVICE_URL)")
else:
self.available = SEARCH_ENABLED
# Use different timeout settings for indexing and search requests # Use different timeout settings for indexing and search requests
self.client = AsyncClient(timeout=30.0, base_url=TXTAI_SERVICE_URL) if self.available:
self.index_client = AsyncClient(timeout=120.0, base_url=TXTAI_SERVICE_URL) self.client = AsyncClient(timeout=30.0, base_url=TXTAI_SERVICE_URL)
self.index_client = AsyncClient(timeout=120.0, base_url=TXTAI_SERVICE_URL)
else:
self.client = None
self.index_client = None
# Initialize search cache # Initialize search cache
self.cache = SearchCache() if SEARCH_CACHE_ENABLED else None self.cache = SearchCache() if SEARCH_CACHE_ENABLED else None
@@ -205,7 +217,7 @@ class SearchService:
async def info(self) -> dict: async def info(self) -> dict:
"""Return information about search service""" """Return information about search service"""
if not self.available: if not self.available or not self.client:
return {"status": "disabled"} return {"status": "disabled"}
try: try:
response: Response = await self.client.get("/info") response: Response = await self.client.get("/info")