Files
gesetzblatt/bgbl_util.py
Michael Hermann 2bb70ae0ed Initiales gesetzblatt-Repo: Parser-Zweig ab 2023 von recht.bund.de.
Historie 1949–2022 bleibt in lawgit, bis der GPU-Lauf endet; Schnitt ohne Lücke.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 11:56:05 +02:00

99 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Gemeinsame Pfade, HTTP-Session und Fundstellen für das gesetzblatt-Repo."""
from __future__ import annotations
import hashlib
import json
import time
from pathlib import Path
import requests
ROOT = Path(__file__).resolve().parent
BGBl_DIR = ROOT / "bgbl"
META_DIR = ROOT / "timeline" / "meta"
TEXT_DIR = ROOT / "timeline" / "text"
PARSED_DIR = ROOT / "timeline" / "parsed"
DATA_DIR = ROOT / "data"
LAWGIT_ROOT = ROOT.parent / "lawgit"
LAWGIT_BGBL = LAWGIT_ROOT / "bgbl"
USER_AGENT = (
"Gesetzblatt/0.1 (+https://git.coded.law/michaelkole/gesetzblatt; "
"Forschungsparser, höflich, kein Massen-Crawler)"
)
SESSION = requests.Session()
SESSION.headers.update({"User-Agent": USER_AGENT})
def kind_to_teil(kind: str) -> str:
return "BGBl-1" if kind == "bgbl1" else "BGBl-2"
def kind_to_rom(kind: str) -> str:
return "I" if kind == "bgbl1" else "II"
def fundstelle(kind: str, year: int, number: int | str, page: int | None = None) -> str:
teil = kind_to_rom(kind)
if year >= 2023:
return f"BGBl. {year} {teil} Nr. {number}"
if page:
return f"BGBl. {year} {teil} S. {page}"
return f"BGBl. {year} {teil} Nr. {number}"
def issue_id(kind: str, year: int, number: int | str) -> str:
return f"{kind}-{year}-{number}-1"
def pdf_path(kind: str, year: int, number: int | str) -> Path:
return BGBl_DIR / kind / str(year) / f"{kind}_{year}_{number}.pdf"
def save_json(path: Path, obj) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(obj, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def load_json(path: Path, default=None):
if not path.exists():
return default
return json.loads(path.read_text(encoding="utf-8"))
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1 << 16), b""):
h.update(chunk)
return h.hexdigest()
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def download_file(url: str, dest: Path, *, timeout: int = 120) -> bool:
"""Datei holen. True = neu geschrieben, False = schon da oder Fehler."""
dest.parent.mkdir(parents=True, exist_ok=True)
if dest.exists() and dest.stat().st_size > 0:
return False
r = SESSION.get(url, timeout=timeout, stream=True)
if r.status_code != 200:
print(f" Fehler HTTP {r.status_code}: {url}")
return False
tmp = dest.with_suffix(dest.suffix + ".part")
with open(tmp, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
f.write(chunk)
tmp.rename(dest)
return True
def polite_sleep(seconds: float = 0.25) -> None:
time.sleep(seconds)