tests-passed

This commit is contained in:
2025-07-31 18:55:59 +03:00
parent b7abb8d8a1
commit e7230ba63c
126 changed files with 8326 additions and 3207 deletions

View File

@@ -13,7 +13,8 @@ def get_diff(original: str, modified: str) -> list[str]:
Returns:
A list of differences.
"""
return list(ndiff(original.split(), modified.split()))
diff = list(ndiff(original.split(), modified.split()))
return [d for d in diff if d.startswith(("+", "-"))]
def apply_diff(original: str, diff: list[str]) -> str:

View File

@@ -2,8 +2,8 @@
JSON encoders and utilities
"""
import datetime
import decimal
import json
from datetime import date, datetime
from typing import Any, Union
import orjson
@@ -11,56 +11,76 @@ import orjson
def default_json_encoder(obj: Any) -> Any:
"""
Default JSON encoder для объектов, которые не поддерживаются стандартным JSON
Кастомный JSON энкодер для сериализации нестандартных типов.
Args:
obj: Объект для сериализации
Returns:
Сериализуемое представление объекта
Сериализованное представление объекта
Raises:
TypeError: Если объект не может быть сериализован
"""
if hasattr(obj, "dict") and callable(obj.dict):
return obj.dict()
if hasattr(obj, "__dict__"):
return obj.__dict__
if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
# Обработка datetime
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, decimal.Decimal):
return float(obj)
serialized = False
# Обработка объектов с методом __json__
if hasattr(obj, "__json__"):
return obj.__json__()
msg = f"Object of type {type(obj)} is not JSON serializable"
raise TypeError(msg)
try:
result = obj.__json__()
serialized = True
return result
except Exception as _e:
serialized = False
# Обработка объектов с методом to_dict
if hasattr(obj, "to_dict"):
try:
result = obj.to_dict()
serialized = True
return result
except Exception as _e:
serialized = False
# Обработка объектов с методом dict
if hasattr(obj, "dict"):
try:
result = obj.dict()
serialized = True
return result
except Exception as _e:
serialized = False
# Если ни один из методов не сработал, вызываем TypeError
if not serialized:
error_text = f"Object of type {type(obj).__name__} is not JSON serializable"
raise TypeError(error_text)
def orjson_dumps(obj: Any, **kwargs: Any) -> bytes:
"""
Сериализует объект в JSON с помощью orjson
Сериализация объекта с помощью orjson.
Args:
obj: Объект для сериализации
**kwargs: Дополнительные параметры для orjson.dumps
**kwargs: Дополнительные параметры
Returns:
bytes: JSON в виде байтов
bytes: Сериализованный объект
"""
# Используем правильную константу для orjson
option_flags = orjson.OPT_SERIALIZE_DATACLASS
if kwargs.get("indent"):
option_flags |= orjson.OPT_INDENT_2
return orjson.dumps(obj, default=default_json_encoder, option=option_flags)
return orjson.dumps(obj, default=default_json_encoder, **kwargs)
def orjson_loads(data: Union[str, bytes]) -> Any:
"""
Десериализует JSON с помощью orjson
Десериализация объекта с помощью orjson.
Args:
data: JSON данные в виде строки или байтов
data: Строка или байты для десериализации
Returns:
Десериализованный объект
@@ -68,51 +88,50 @@ def orjson_loads(data: Union[str, bytes]) -> Any:
return orjson.loads(data)
class JSONEncoder:
"""Кастомный JSON кодировщик на основе orjson"""
@staticmethod
def encode(obj: Any) -> str:
"""Encode object to JSON string"""
return orjson_dumps(obj).decode("utf-8")
@staticmethod
def decode(data: Union[str, bytes]) -> Any:
"""Decode JSON string to object"""
return orjson_loads(data)
# Создаем экземпляр для обратной совместимости
CustomJSONEncoder = JSONEncoder()
def fast_json_dumps(obj: Any, indent: bool = False) -> str:
class JSONEncoder(json.JSONEncoder):
"""
Быстрая сериализация JSON
Расширенный JSON энкодер с поддержкой кастомной сериализации.
"""
def default(self, obj: Any) -> Any:
"""
Метод для сериализации нестандартных типов.
Args:
obj: Объект для сериализации
Returns:
Сериализованное представление объекта
"""
try:
return default_json_encoder(obj)
except TypeError:
return super().default(obj)
def fast_json_dumps(obj: Any, **kwargs: Any) -> str:
"""
Быстрая сериализация объекта в JSON-строку.
Args:
obj: Объект для сериализации
indent: Форматировать с отступами
**kwargs: Дополнительные параметры
Returns:
JSON строка
str: JSON-строка
"""
return orjson_dumps(obj, indent=indent).decode("utf-8")
return json.dumps(obj, cls=JSONEncoder, **kwargs)
def fast_json_loads(data: Union[str, bytes]) -> Any:
def fast_json_loads(data: str, **kwargs: Any) -> Any:
"""
Быстрая десериализация JSON
Быстрая десериализация JSON-строки.
Args:
data: JSON данные
data: JSON-строка
**kwargs: Дополнительные параметры
Returns:
Десериализованный объект
"""
return orjson_loads(data)
# Экспортируем для удобства
dumps = fast_json_dumps
loads = fast_json_loads
return json.loads(data, **kwargs)

View File

@@ -2,66 +2,54 @@
Модуль для обработки HTML-фрагментов
"""
import trafilatura
from utils.logger import root_logger as logger
import re
from typing import Optional
def extract_text(html: str) -> str:
def extract_text(html_content: Optional[str]) -> str:
"""
Извлекает чистый текст из HTML
Извлекает текст из HTML с помощью регулярных выражений.
Args:
html: HTML строка
html_content (Optional[str]): HTML-строка для извлечения текста
Returns:
str: Извлеченный текст или пустая строка
"""
try:
result = trafilatura.extract(
html,
include_comments=False,
include_tables=True,
include_formatting=False,
favor_precision=True,
)
return result or ""
except Exception as e:
logger.error(f"Error extracting text: {e}")
if not html_content:
return ""
# Удаляем HTML-теги
text = re.sub(r"<[^>]+>", " ", html_content)
# Декодируем HTML-сущности
text = re.sub(r"&[a-zA-Z]+;", " ", text)
# Заменяем несколько пробелов на один
text = re.sub(r"\s+", " ", text).strip()
return text
def wrap_html_fragment(fragment: str) -> str:
"""
Оборачивает HTML-фрагмент в полную HTML-структуру для корректной обработки.
Оборачивает HTML-фрагмент в полный HTML-документ.
Args:
fragment: HTML-фрагмент для обработки
fragment (str): HTML-фрагмент
Returns:
str: Полный HTML-документ
Example:
>>> wrap_html_fragment("<p>Текст параграфа</p>")
'<!DOCTYPE html><html><head><meta charset="utf-8"></head><body><p>Текст параграфа</p></body></html>'
"""
if not fragment or not fragment.strip():
if "<!DOCTYPE html>" in fragment and "<html>" in fragment:
return fragment
# Проверяем, является ли контент полным HTML-документом
is_full_html = fragment.strip().startswith("<!DOCTYPE") or fragment.strip().startswith("<html")
# Если это фрагмент, оборачиваем его в полный HTML-документ
if not is_full_html:
return f"""<!DOCTYPE html>
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
{fragment}
</body>
</html>"""
return fragment

View File

@@ -5,11 +5,21 @@ from auth.orm import Author
from services.db import local_session
def replace_translit(src: str) -> str:
ruchars = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя."
enchars = "abvgdeyozhziyklmnoprstufhcchshsch'yye'yuyaa-"
def replace_translit(src: str | None) -> str:
"""
Транслитерация строки с русского на английский.
# Создаем словарь для замены, так как некоторые русские символы соответствуют нескольким латинским
Args:
src (str | None): Исходная строка или None
Returns:
str: Транслитерированная строка или пустая строка, если src None
"""
if src is None:
return ""
# Создаем словарь для замены, так как некоторые русские символы
# соответствуют нескольким латинским
translit_dict = {
"а": "a",
"б": "b",
@@ -48,7 +58,7 @@ def replace_translit(src: str) -> str:
}
result = ""
for char in src:
for char in src.lower():
result += translit_dict.get(char, char)
return result