sdlc-hub/src/jira_hub/core/projects.py
Andrey Epifancev c9ac2d5238 refactor: очередь ядра и обработчики команд модуля jira (#87)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 22:15:42 +04:00

82 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Проекты-метки: таблица project, проверка метки, создание и список."""
import unicodedata
from . import db
MAX_LEN = 100
_FORBIDDEN = ',"\'()\\'
_FIELDS = "label, name, board_id, board_type, created_at" # долг hub-modules: проекты → модуль jira (change wiki)
class ProjectError(ValueError):
"""Ошибка данных проекта; field — label | name | board_id."""
def __init__(self, field: str, reason: str):
super().__init__(f"{field}: {reason}")
self.field = field
class ProjectExists(ProjectError):
def __init__(self, label: str):
super().__init__("label", f"проект с такой меткой уже есть: {label}")
def label_error(label) -> str | None:
if not isinstance(label, str) or not label:
return "ожидается непустая строка"
if len(label) > MAX_LEN:
return f"длиннее {MAX_LEN} символов"
if any(c.isspace() or c in _FORBIDDEN or unicodedata.category(c) == "Cc" for c in label):
return "пробелы, управляющие символы и символы , \" ' ( ) \\ недопустимы"
return None
def validate(label, name, board_id) -> tuple[str, str, int | None]:
"""Проверить поля проекта; вернуть (метка, название без пробелов по краям, доска)."""
error = label_error(label)
if error:
raise ProjectError("label", error)
if not isinstance(name, str) or not name.strip():
raise ProjectError("name", "ожидается непустая строка")
if len(name.strip()) > MAX_LEN:
raise ProjectError("name", f"длиннее {MAX_LEN} символов")
# долг hub-modules: проекты → модуль jira (change wiki)
if board_id is not None and (not isinstance(board_id, int) or isinstance(board_id, bool) or board_id <= 0):
raise ProjectError("board_id", "ожидается целое > 0 или null")
return label, name.strip(), board_id
def get(conn, label: str) -> dict | None:
row = conn.execute(f"SELECT {_FIELDS} FROM project WHERE label = ?", (label,)).fetchone()
return None if row is None else dict(row)
def create(conn, label: str, name: str, board_id: int | None = None) -> dict:
label, name, board_id = validate(label, name, board_id)
with db.transaction(conn):
if label.casefold() in {x.casefold() for x in labels(conn)}:
raise ProjectExists(label)
# долг hub-modules: проекты → модуль jira (change wiki)
conn.execute("INSERT INTO project(label, name, board_id, board_type, created_at) VALUES (?, ?, ?, NULL, ?)",
(label, name, board_id, db.now()))
return get(conn, label)
def list_projects(conn) -> list[dict]:
# долг hub-modules: проекты → модуль jira (change wiki)
rows = conn.execute(
f"SELECT {_FIELDS}, (SELECT COUNT(*) FROM issue i WHERE EXISTS "
"(SELECT 1 FROM json_each(i.labels) WHERE value = project.label)) AS issues_count "
"FROM project ORDER BY created_at, label").fetchall()
return [dict(r) for r in rows]
def light_list(conn) -> list[dict]:
"""Проекты без issues_count (label, board_id, board_type), порядок как у list_projects."""
# долг hub-modules: проекты → модуль jira (change wiki)
return [dict(r) for r in conn.execute("SELECT label, board_id, board_type FROM project ORDER BY created_at, label")]
def labels(conn) -> list[str]:
return [r["label"] for r in conn.execute("SELECT label FROM project ORDER BY created_at, label")]