Files
VacatureRadar/scripts/package_project.py
T
Jens b8091e59bd
deploy / deploy (push) Canceled after 0s
Initial deploy setup
2026-07-21 14:00:00 +02:00

122 lines
3.6 KiB
Python

#!/usr/bin/env python
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
import tempfile
import zipfile
from collections.abc import Iterable
from datetime import UTC, datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
ROOT_NAME = "VacatureRadar_Project"
EXCLUDED_DIRS = {
".git",
".venv",
".pytest_cache",
".ruff_cache",
"__pycache__",
"backups",
"htmlcov",
"logs",
"media",
"staticfiles",
}
EXCLUDED_FILES = {".coverage", ".env", "celerybeat-schedule", "db.sqlite3"}
EXCLUDED_SUFFIXES = {".pyc", ".pyo"}
def is_included(path: Path) -> bool:
relative = path.relative_to(ROOT)
if any(part in EXCLUDED_DIRS for part in relative.parts):
return False
if path.name in EXCLUDED_FILES or path.suffix in EXCLUDED_SUFFIXES:
return False
if relative.parts and relative.parts[0] == "local" and path.name != ".gitkeep":
return False
return path.is_file()
def iter_files() -> Iterable[Path]:
return sorted(path for path in ROOT.rglob("*") if is_included(path))
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def write_manifest(staging_root: Path) -> None:
entries = []
for path in sorted(staging_root.rglob("*")):
if not path.is_file() or path.name == "PROJECT_MANIFEST.json":
continue
entries.append(
{
"path": path.relative_to(staging_root).as_posix(),
"bytes": path.stat().st_size,
"sha256": sha256(path),
}
)
manifest = {
"project": "VacatureRadar",
"artifact_root": ROOT_NAME,
"generated_at": datetime.now(UTC).isoformat(),
"file_count_excluding_manifest": len(entries),
"total_bytes_excluding_manifest": sum(item["bytes"] for item in entries),
"files": entries,
}
(staging_root / "PROJECT_MANIFEST.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
def package(output: Path) -> tuple[int, str]:
output = output.resolve()
output.parent.mkdir(parents=True, exist_ok=True)
if output.exists():
output.unlink()
with tempfile.TemporaryDirectory(prefix="vacatureradar-package-") as temporary:
staging_root = Path(temporary) / ROOT_NAME
staging_root.mkdir()
for source in iter_files():
relative = source.relative_to(ROOT)
destination = staging_root / relative
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
write_manifest(staging_root)
with zipfile.ZipFile(
output, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
) as archive:
for path in sorted(staging_root.rglob("*")):
if path.is_file():
archive.write(path, path.relative_to(staging_root.parent))
return output.stat().st_size, sha256(output)
def main() -> int:
parser = argparse.ArgumentParser(description="Maak een schone VacatureRadar-project-ZIP")
parser.add_argument(
"output",
nargs="?",
type=Path,
default=ROOT.parent / "VacatureRadar_Project.zip",
)
args = parser.parse_args()
size, checksum = package(args.output)
print(f"ZIP: {args.output.resolve()}")
print(f"Bytes: {size}")
print(f"SHA256: {checksum}")
return 0
if __name__ == "__main__":
raise SystemExit(main())