82 lines
3.7 KiB
Python
82 lines
3.7 KiB
Python
"""Проекты-метки: таблица 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")]
|