set -euo pipefail python3 - <<'PY' import re from pathlib import Path def fix_file(p: Path) -> bool: s = p.read_text() lines = s.splitlines(keepends=True) changed = False # собрать все строки future-импортов и убрать их fut_pat = re.compile(r'^\s*from\s+__future__\s+import\s+annotations\s*(#.*)?$\n?', re.M) if fut_pat.search(s): new_lines = [] for ln in lines: if not fut_pat.match(ln): new_lines.append(ln) lines = new_lines changed = True # определить позицию вставки future (сразу после верхнего докстринга, если он есть) i = 0 # пропускаем пустые строки и комментарии/кодировки/шейбанг while i < len(lines) and ( lines[i].strip() == "" or lines[i].lstrip().startswith("#!") or re.match(r'^\s*#.*coding[:=]', lines[i]) ): i += 1 insert_at = i # если на вершине модульный докстринг ("""...""" или '''...''') if i < len(lines) and re.match(r'^\s*[rubfRUBF]*[\'"]{3}', lines[i]): quote = '"""' if '"""' in lines[i] else "'''" j = i # ищем закрывающую кавычку while j < len(lines): if quote in lines[j] and j != i: j += 1 break j += 1 insert_at = min(j, len(lines)) # вставляем future-импорт future_line = "from __future__ import annotations\n" if not any(l.strip().startswith("from __future__ import annotations") for l in lines): lines.insert(insert_at, future_line) changed = True # обеспечить корректное положение "from uuid import UUID": txt = "".join(lines) uuid_pat = re.compile(r'^\s*from\s+uuid\s+import\s+UUID\s*(#.*)?$\n?', re.M) has_uuid = uuid_pat.search(txt) is not None if has_uuid: # удалим все вхождения, потом вставим одно — сразу после future new_lines = [] removed = False for ln in lines: if uuid_pat.match(ln): removed = True continue new_lines.append(ln) lines = new_lines if removed: lines.insert(insert_at + 1, "from uuid import UUID\n") changed = True else: # импорт отсутствует — добавлять имеет смысл только если в файле встречается "UUID" как тип if re.search(r'\bUUID\b', "".join(lines)): lines.insert(insert_at + 1, "from uuid import UUID\n") changed = True if changed: p.write_text("".join(lines)) return changed changed_any = False for sub in ("services/auth/src/app/schemas", "services/match/src/app/schemas", "services/profiles/src/app/schemas"): d = Path(sub) if not d.exists(): continue for p in d.rglob("*.py"): if fix_file(p): print("fixed:", p) changed_any = True print("DONE" if changed_any else "NO-CHANGES") PY echo "[docker] rebuild & restart auth/match/profiles…" docker compose build auth match profiles >/dev/null docker compose restart auth match profiles