add linters

This commit is contained in:
Pavel Sobolev
2025-11-13 01:32:17 +03:00
parent c4bb087aaf
commit d5ff05abdb
28 changed files with 2070 additions and 331 deletions

View File

@@ -1,21 +1,43 @@
"""Рендеринг резюме в формат DOCX.
Класс `DocxRenderer` формирует документ с заголовками, контактами, опытом
и навыками на основе сериализованных данных профиля.
"""
from __future__ import annotations
import logging
from collections.abc import Sequence
from io import BytesIO
from typing import Any
from docx import Document
from docx.shared import Pt, RGBColor
from docx import Document as new_document
from docx.document import Document as DocxDocument
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Pt, RGBColor
from cv.services.dowload.pdf import ProfileSerializer
from cv.services.dowload.base import ProfileSerializer
from resume.utils.logging import configure_root_logger
_root = logging.getLogger()
if not _root.handlers:
configure_root_logger()
logger = logging.getLogger(__name__)
class DocxRenderer:
def __init__(self, ):
"""Создает DOCX-документ на основе данных профиля."""
def __init__(
self,
) -> None:
"""Инициализирует сериализатор документов DOCX."""
self.serializer = ProfileSerializer()
@staticmethod
def _add_divider(document: Document) -> None:
def _add_divider(document: DocxDocument) -> None:
"""Добавить горизонтальный разделитель как границу параграфа."""
p = document.add_paragraph()
fmt = p.paragraph_format
fmt.space_before = Pt(6)
@@ -32,38 +54,45 @@ class DocxRenderer:
pPr.append(pBdr)
@staticmethod
def _month_year(dt) -> str:
def _month_year(dt: Any) -> str:
"""Вернуть строку месяца и года на русском для даты."""
ru_months = {
1: "январь", 2: "февраль", 3: "март", 4: "апрель",
5: "май", 6: "июнь", 7: "июль", 8: "август",
9: "сентябрь", 10: "октябрь", 11: "ноябрь", 12: "декабрь",
1: "январь",
2: "февраль",
3: "март",
4: "апрель",
5: "май",
6: "июнь",
7: "июль",
8: "август",
9: "сентябрь",
10: "октябрь",
11: "ноябрь",
12: "декабрь",
}
return f"{ru_months.get(dt.month, dt.strftime('%B')).capitalize()} {dt.year}"
def render(self, profile) -> BytesIO:
data = self.serializer.serialize(profile)
buf = BytesIO()
doc = Document()
# Заголовок
def _append_header(self, doc: DocxDocument, data: dict[str, Any]) -> None:
"""Добавить заголовок и краткие сведения."""
doc.core_properties.title = data["full_name"]
if data["role"]:
if data.get("role"):
doc.core_properties.subject = data["role"]
doc.add_heading(data["full_name"], level=0)
if data["role"]:
if data.get("role"):
doc.add_paragraph(data["role"])
if data["summary"]:
if data.get("summary"):
doc.add_paragraph(data["summary"])
meta_parts: list[str] = []
if data["location"]:
if data.get("location"):
meta_parts.append(data["location"])
if data["languages"]:
if data.get("languages"):
meta_parts.append("Языки: " + ", ".join(data["languages"]))
if meta_parts:
doc.add_paragraph("".join(meta_parts))
# Контакты
def _append_contacts(self, doc: DocxDocument, contacts: dict[str, Any]) -> None:
"""Добавить секцию контактов."""
doc.add_heading("Контакты", level=1)
contacts = data["contacts"]
if contacts.get("email"):
doc.add_paragraph(f"Email: {contacts['email']}")
if contacts.get("phone"):
@@ -71,12 +100,17 @@ class DocxRenderer:
if contacts.get("telegram"):
doc.add_paragraph(f"Telegram: {contacts['telegram']}")
# Опыт
def _append_experience(self, doc: DocxDocument, experience: Sequence[Any] | None) -> None:
"""Добавить опыт работы."""
doc.add_heading("Опыт работы", level=1)
for i, e in enumerate(data["experience"]):
for i, e in enumerate(experience or []):
if i:
self._add_divider(doc)
period = f"{self._month_year(e.start_date)}{(self._month_year(e.end_date) if e.end_date else 'настоящее время')}"
start = self._month_year(e.start_date) if getattr(e, "start_date", None) else ""
end = (
self._month_year(e.end_date) if getattr(e, "end_date", None) else "настоящее время"
)
period = f"{start}{end}"
p_company = doc.add_paragraph()
run_company = p_company.add_run(e.company)
run_company.bold = True
@@ -87,19 +121,42 @@ class DocxRenderer:
r.font.color.rgb = RGBColor(0x99, 0xA2, 0xB2)
if e.summary:
doc.add_paragraph(e.summary)
for a in (e.achievements or []):
for a in e.achievements or []:
if a:
doc.add_paragraph(a, style="List Bullet")
if e.tech:
doc.add_paragraph("Технологии: " + ", ".join([t for t in e.tech if t]))
# Навыки
def _append_skills(self, doc: DocxDocument, skills: Sequence[Any] | None) -> None:
"""Добавить навыки."""
doc.add_heading("Навыки", level=1)
for g in data["skills_map"]:
for g in skills or []:
if g.items:
doc.add_paragraph(f"{g.group}: " + ", ".join([i for i in g.items if i]))
def render(self, profile: Any) -> BytesIO:
"""Сформировать DOCX документ по профилю.
Args:
profile: Объект профиля.
Returns:
BytesIO: Буфер с содержимым DOCX.
"""
logger.info("Старт рендеринга DOCX для профиля")
data = self.serializer.serialize(profile)
buf = BytesIO()
doc: DocxDocument = new_document()
# Заголовок
self._append_header(doc, data)
# Контакты
self._append_contacts(doc, data.get("contacts", {}))
# Опыт
self._append_experience(doc, data.get("experience"))
# Навыки
self._append_skills(doc, data.get("skills_map"))
doc.save(buf)
buf.seek(0)
logger.info("DOCX сгенерирован: %s байт", buf.getbuffer().nbytes)
return buf