tests-passed
This commit is contained in:
@@ -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:
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user