Refactor digitador bot and add runtime monitoring

This commit is contained in:
erik
2026-08-30 14:02:58 -05:00
parent 3b01456875
commit d9e2012e22
46 changed files with 1720 additions and 248 deletions
+83
View File
@@ -0,0 +1,83 @@
"""Construye un ZIP reproducible y limpio del Bot Digitador."""
from __future__ import annotations
import argparse
import hashlib
import sys
from pathlib import Path
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from bot_digitador.core.config import REQUIRED_TEMPLATE_NAMES # noqa: E402
DEFAULT_OUTPUT = ROOT.parent / "bot_digitador.zip"
TOP_LEVEL_FILES = (".env.example", "README.md", "requirements.txt", "run_gui.py", "main.py")
EXCLUDED_PARTS = {"__pycache__", ".pytest_cache", ".venv", "data", "reportes_bot", "out"}
EXCLUDED_SUFFIXES = {".pyc", ".pyo", ".log"}
def package_files(root: Path) -> list[Path]:
files: list[Path] = []
for name in TOP_LEVEL_FILES:
path = root / name
if path.is_file():
files.append(path)
for folder in (root / "bot_digitador", root / "template"):
if not folder.exists():
continue
for path in folder.rglob("*"):
relative = path.relative_to(root)
if not path.is_file() or any(part in EXCLUDED_PARTS for part in relative.parts):
continue
if path.suffix.lower() in EXCLUDED_SUFFIXES or path.name == ".env":
continue
files.append(path)
return sorted(files, key=lambda path: path.relative_to(root).as_posix())
def validate_assets(root: Path) -> None:
missing = [name for name in REQUIRED_TEMPLATE_NAMES if not (root / "template" / name).is_file()]
if missing:
raise RuntimeError(
"No se puede crear un paquete ejecutable. Faltan plantillas: " + ", ".join(missing)
)
def build_package(root: Path = ROOT, output: Path = DEFAULT_OUTPUT, *, require_templates: bool = True) -> Path:
if require_templates:
validate_assets(root)
output.parent.mkdir(parents=True, exist_ok=True)
with ZipFile(output, "w", compression=ZIP_DEFLATED, compresslevel=9) as archive:
for source in package_files(root):
relative = source.relative_to(root).as_posix()
info = ZipInfo(f"bot_digitador/{relative}", date_time=(2020, 1, 1, 0, 0, 0))
info.compress_type = ZIP_DEFLATED
info.external_attr = 0o644 << 16
archive.writestr(info, source.read_bytes())
return output
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
parser.add_argument("--allow-missing-templates", action="store_true")
args = parser.parse_args()
target = build_package(output=args.output.resolve(), require_templates=not args.allow_missing_templates)
print(f"Paquete: {target}")
print(f"SHA256: {sha256(target)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+57
View File
@@ -0,0 +1,57 @@
"""Construye y publica el Bot Digitador en el prefijo latest de MinIO."""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
import boto3
from botocore.config import Config
from package_bot import DEFAULT_OUTPUT, build_package, sha256
DEFAULT_ENDPOINT = "https://s3.builker.xyz"
DEFAULT_BUCKET = "apps-versions"
DEFAULT_KEY = "BotDigitadorPYSCuentasMedicas/latest/bot_digitador.zip"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
parser.add_argument("--build-only", action="store_true")
args = parser.parse_args()
artifact = build_package(output=args.output.resolve(), require_templates=True)
print(f"Paquete validado: {artifact} ({sha256(artifact)})")
if args.build_only:
return 0
access = os.getenv("MINIO_ACCESS_KEY") or os.getenv("AWS_ACCESS_KEY_ID")
secret = os.getenv("MINIO_SECRET_KEY") or os.getenv("AWS_SECRET_ACCESS_KEY")
if not access or not secret:
print("Faltan MINIO_ACCESS_KEY/MINIO_SECRET_KEY para publicar.", file=sys.stderr)
return 2
endpoint = os.getenv("MINIO_ENDPOINT", DEFAULT_ENDPOINT).rstrip("/")
bucket = os.getenv("MINIO_APPS_BUCKET", DEFAULT_BUCKET)
key = os.getenv("DIGITADOR_PACKAGE_KEY", DEFAULT_KEY).strip("/")
client = boto3.client(
"s3",
endpoint_url=endpoint,
aws_access_key_id=access,
aws_secret_access_key=secret,
region_name=os.getenv("MINIO_REGION", "us-east-1"),
config=Config(s3={"addressing_style": "path"}),
)
client.upload_file(
str(artifact),
bucket,
key,
ExtraArgs={"ContentType": "application/zip"},
)
print(f"Publicado: {endpoint}/{bucket}/{key}")
return 0
if __name__ == "__main__":
raise SystemExit(main())