blitzid 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. blitzid-0.1.0/.dockerignore +9 -0
  2. blitzid-0.1.0/.env.example +37 -0
  3. blitzid-0.1.0/.github/workflows/ci.yml +31 -0
  4. blitzid-0.1.0/.github/workflows/docker.yml +62 -0
  5. blitzid-0.1.0/.github/workflows/publish.yml +31 -0
  6. blitzid-0.1.0/.gitignore +59 -0
  7. blitzid-0.1.0/.pre-commit-config.yaml +14 -0
  8. blitzid-0.1.0/CHANGELOG.md +138 -0
  9. blitzid-0.1.0/CONTRIBUTING.md +39 -0
  10. blitzid-0.1.0/Dockerfile +22 -0
  11. blitzid-0.1.0/LICENSE +21 -0
  12. blitzid-0.1.0/PKG-INFO +413 -0
  13. blitzid-0.1.0/README.md +353 -0
  14. blitzid-0.1.0/docker-compose.yml +28 -0
  15. blitzid-0.1.0/docs/coding-standard.md +169 -0
  16. blitzid-0.1.0/images/README.md +12 -0
  17. blitzid-0.1.0/images/bub_der_personalausweis_kopie.jpg +0 -0
  18. blitzid-0.1.0/images/nl_td1_id_specimen.jpg +0 -0
  19. blitzid-0.1.0/images/ny_license_plate_wrap.jpg +0 -0
  20. blitzid-0.1.0/images/nys_driver_license_sample.webp +0 -0
  21. blitzid-0.1.0/images/solvay_conference_1927.jpg +0 -0
  22. blitzid-0.1.0/images/td3_passport_specimen.jpg +0 -0
  23. blitzid-0.1.0/images/tlc_driver_license_sample.png +0 -0
  24. blitzid-0.1.0/models/.gitkeep +0 -0
  25. blitzid-0.1.0/models/README.md +34 -0
  26. blitzid-0.1.0/pyproject.toml +122 -0
  27. blitzid-0.1.0/scripts/README.md +73 -0
  28. blitzid-0.1.0/scripts/benchmark.py +231 -0
  29. blitzid-0.1.0/scripts/demo_cli.py +175 -0
  30. blitzid-0.1.0/scripts/download_models.py +64 -0
  31. blitzid-0.1.0/scripts/face_detector_demo.py +267 -0
  32. blitzid-0.1.0/scripts/midv500.py +632 -0
  33. blitzid-0.1.0/scripts/ocr_demo.py +81 -0
  34. blitzid-0.1.0/src/blitzid/__init__.py +49 -0
  35. blitzid-0.1.0/src/blitzid/_image.py +180 -0
  36. blitzid-0.1.0/src/blitzid/_models.py +142 -0
  37. blitzid-0.1.0/src/blitzid/api/__init__.py +11 -0
  38. blitzid-0.1.0/src/blitzid/api/_analyze.py +129 -0
  39. blitzid-0.1.0/src/blitzid/api/_app.py +429 -0
  40. blitzid-0.1.0/src/blitzid/api/_jobs.py +399 -0
  41. blitzid-0.1.0/src/blitzid/api/_schemas.py +35 -0
  42. blitzid-0.1.0/src/blitzid/api/_upload.py +89 -0
  43. blitzid-0.1.0/src/blitzid/exceptions.py +17 -0
  44. blitzid-0.1.0/src/blitzid/face/__init__.py +5 -0
  45. blitzid-0.1.0/src/blitzid/face/_face.py +122 -0
  46. blitzid-0.1.0/src/blitzid/face/_scrfd.py +151 -0
  47. blitzid-0.1.0/src/blitzid/face/detector.py +481 -0
  48. blitzid-0.1.0/src/blitzid/py.typed +0 -0
  49. blitzid-0.1.0/src/blitzid/reading/__init__.py +5 -0
  50. blitzid-0.1.0/src/blitzid/reading/document.py +399 -0
  51. blitzid-0.1.0/src/blitzid/reading/mrz.py +355 -0
  52. blitzid-0.1.0/src/blitzid/reading/ocr.py +152 -0
  53. blitzid-0.1.0/tests/conftest.py +56 -0
  54. blitzid-0.1.0/tests/test_api.py +1006 -0
  55. blitzid-0.1.0/tests/test_detector.py +256 -0
  56. blitzid-0.1.0/tests/test_detector_helpers.py +314 -0
  57. blitzid-0.1.0/tests/test_document.py +197 -0
  58. blitzid-0.1.0/tests/test_exceptions.py +42 -0
  59. blitzid-0.1.0/tests/test_image.py +185 -0
  60. blitzid-0.1.0/tests/test_models.py +109 -0
  61. blitzid-0.1.0/tests/test_mrz.py +360 -0
  62. blitzid-0.1.0/tests/test_ocr.py +118 -0
  63. blitzid-0.1.0/tests/test_refactored.py +99 -0
  64. blitzid-0.1.0/uv.lock +1670 -0
@@ -0,0 +1,9 @@
1
+ .git
2
+ .venv
3
+ .kilo
4
+ .env
5
+ models/
6
+ images/
7
+ results/
8
+ __pycache__/
9
+ *.pyc
@@ -0,0 +1,37 @@
1
+ # blitzid environment variables — copy to .env and adjust.
2
+ # All optional; unset means default behavior.
3
+
4
+ # Directory for all model weights (SCRFD + RapidOCR). Default: the
5
+ # platformdirs user cache (~/.cache/blitzid/models on Linux).
6
+ # For docker compose: point at a mounted volume so containers share
7
+ # one copy instead of re-downloading on every start, e.g.:
8
+ # volumes: [ "./models:/models" ]
9
+ # environment: { BLITZID_MODELS_DIR: /models }
10
+ #BLITZID_MODELS_DIR=/models
11
+
12
+ # Set to 1 to run the OCR/MRZ integration tests (downloads RapidOCR
13
+ # models, needs network). Unit test suites skip them otherwise.
14
+ #BLITZID_OCR_INTEGRATION=1
15
+
16
+ # ── HTTP API (blitzid[api] extra) ─────────────────────────────
17
+
18
+ # Redis the API's job store talks to. The docker-compose redis
19
+ # (persistence disabled — jobs live in RAM only) is wired for you;
20
+ # override only for external Redis instances.
21
+ #BLITZID_API_REDIS_URL=redis://redis:6379/0
22
+
23
+ # Upload size cap in MB (413 above it).
24
+ #BLITZID_API_MAX_UPLOAD_MB=20
25
+
26
+ # Lifetime of every job key in seconds (result is 410 Gone after).
27
+ #BLITZID_API_JOB_TTL_SECONDS=900
28
+
29
+ # Queue capacity (503 + Retry-After when full).
30
+ #BLITZID_API_MAX_QUEUED_JOBS=50
31
+
32
+ # Analyses running at once per API process.
33
+ #BLITZID_API_MAX_CONCURRENT_JOBS=2
34
+
35
+ # Claim lease in seconds — a worker crash past this lease re-runs the
36
+ # job instead of losing it (lease sweeper).
37
+ #BLITZID_API_JOB_LEASE_SECONDS=120
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ workflow_call:
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6.0.2
14
+ - uses: astral-sh/setup-uv@v8.1.0
15
+ - run: uv sync --extra dev
16
+ - run: uv run ruff check src/ tests/ scripts/
17
+ - run: uv run ruff format --check src/ tests/ scripts/
18
+ - run: uv run mypy src/ scripts/
19
+
20
+ test:
21
+ runs-on: ubuntu-latest
22
+ strategy:
23
+ matrix:
24
+ python-version: ["3.12", "3.13", "3.14"]
25
+ steps:
26
+ - uses: actions/checkout@v6.0.2
27
+ - uses: astral-sh/setup-uv@v8.1.0
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+ - run: uv sync --extra dev
31
+ - run: uv run pytest tests/ -v
@@ -0,0 +1,62 @@
1
+ name: Docker
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ test:
9
+ uses: ./.github/workflows/ci.yml
10
+
11
+ release:
12
+ needs: test
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ packages: write
17
+ steps:
18
+ - uses: actions/checkout@v6.0.2
19
+
20
+ - name: Build image (amd64)
21
+ run: |
22
+ docker build \
23
+ -t ghcr.io/malvavisc0/blitzid:$GITHUB_REF_NAME \
24
+ -t ghcr.io/malvavisc0/blitzid:latest .
25
+
26
+ - name: Smoke test (compose up, health, analyze, crop)
27
+ env:
28
+ BLITZID_IMAGE_TAG: ${{ github.ref_name }}
29
+ run: |
30
+ docker compose up -d --no-build --wait
31
+ test "$(docker compose images -q api)" = "$(docker image inspect -f '{{.Id}}' ghcr.io/malvavisc0/blitzid:$GITHUB_REF_NAME)"
32
+ curl -fsS http://localhost:8000/health | grep -q '"status":"ok"'
33
+ JOB=$(curl -fsS \
34
+ -F image=@images/nl_td1_id_specimen.jpg \
35
+ -F types=face,ocr,mrz http://localhost:8000/analyze)
36
+ ID=$(echo "$JOB" | jq -r .job_id)
37
+ for _ in $(seq 1 60); do
38
+ RESULT=$(curl -fsS http://localhost:8000/jobs/$ID)
39
+ STATUS=$(echo "$RESULT" | jq -r .status)
40
+ if [ "$STATUS" = "done" ] || [ "$STATUS" = "failed" ]; then break; fi
41
+ sleep 1
42
+ done
43
+ echo "$RESULT" | jq .
44
+ test "$STATUS" = "done"
45
+ echo "$RESULT" | grep -q "DE BRUIJN"
46
+ curl -fsS \
47
+ -F image=@images/nl_td1_id_specimen.jpg \
48
+ -F side=front http://localhost:8000/crop \
49
+ | jq -e '.face_found == true' > /dev/null
50
+ docker compose down -v
51
+
52
+ - name: Login to GHCR
53
+ uses: docker/login-action@v3
54
+ with:
55
+ registry: ghcr.io
56
+ username: ${{ github.actor }}
57
+ password: ${{ secrets.GITHUB_TOKEN }}
58
+
59
+ - name: Push image
60
+ run: |
61
+ docker push ghcr.io/malvavisc0/blitzid:$GITHUB_REF_NAME
62
+ docker push ghcr.io/malvavisc0/blitzid:latest
@@ -0,0 +1,31 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ test:
9
+ uses: ./.github/workflows/ci.yml
10
+
11
+ publish:
12
+ needs: test
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+ permissions:
16
+ contents: read
17
+ id-token: write
18
+ steps:
19
+ - uses: actions/checkout@v6.0.2
20
+ - uses: astral-sh/setup-uv@v8.1.0
21
+
22
+ - name: Verify tag matches the package version
23
+ run: |
24
+ VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
25
+ test "v$VERSION" = "$GITHUB_REF_NAME"
26
+
27
+ - name: Build sdist and wheel
28
+ run: uv build
29
+
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,59 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environments
7
+ .venv/
8
+ venv/
9
+ ENV/
10
+
11
+ # Packaging / build
12
+ build/
13
+ dist/
14
+ *.egg-info/
15
+ .eggs/
16
+
17
+ # Testing / coverage
18
+ .pytest_cache/
19
+ .coverage
20
+ coverage.xml
21
+ htmlcov/
22
+
23
+ # Type checking / linters
24
+ .mypy_cache/
25
+ .pyre/
26
+ .ruff_cache/
27
+
28
+ # Jupyter
29
+ .ipynb_checkpoints/
30
+
31
+ # IDEs / editors
32
+ .vscode/
33
+ .idea/
34
+ *.swp
35
+ *.swo
36
+
37
+ # OS files
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Project artifacts
42
+ results/
43
+
44
+ # Large model files (downloaded at runtime)
45
+ models/**
46
+ !models/
47
+ !models/.gitkeep
48
+ !models/README.md
49
+
50
+ # Logs
51
+ *.log
52
+
53
+ # Env files
54
+ .env
55
+ .env.*
56
+ !.env.example
57
+
58
+ results/*
59
+ data/*
@@ -0,0 +1,14 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.12
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: https://github.com/pre-commit/pre-commit-hooks
9
+ rev: v6.0.0
10
+ hooks:
11
+ - id: trailing-whitespace
12
+ - id: end-of-file-fixer
13
+ - id: check-yaml
14
+ - id: check-added-large-files
@@ -0,0 +1,138 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] - 2026-10-01
8
+
9
+ ### Added
10
+
11
+ - HTTP API (`blitzid[api]` extra: fastapi, uvicorn, python-multipart,
12
+ redis) exposing the analyses as async jobs: `POST /analyze`
13
+ (multipart upload + `types` form field; eager validation with
14
+ 422/400/413/415/503), `GET /jobs/{id}` (claim-once results via
15
+ GETDEL — later reads get `410 Gone`; TTL-bounded), `GET /health`
16
+ for orchestration (`503` when the job store is unreachable). Jobs
17
+ run in per-process worker threads bounded by
18
+ `BLITZID_API_MAX_CONCURRENT_JOBS` with per-engine locks; a Redis
19
+ queue (persistence off — RAM only) with an atomically enforced cap,
20
+ TTL, raw-byte job payloads, and token-owned claims: worker
21
+ heartbeats extend the claim lease, a lost claim discards that
22
+ worker's result (the re-claimed worker's result wins), and the
23
+ claim-lease sweeper re-runs only jobs whose worker actually died. Env
24
+ knobs: `BLITZID_API_REDIS_URL`, `BLITZID_API_MAX_UPLOAD_MB`,
25
+ `BLITZID_API_JOB_TTL_SECONDS`, `BLITZID_API_MAX_QUEUED_JOBS`,
26
+ `BLITZID_API_MAX_CONCURRENT_JOBS`, `BLITZID_API_JOB_LEASE_SECONDS`.
27
+ - Docker deployment: multi-stage `Dockerfile` (weights baked to
28
+ `/models`, non-root, python-urllib HEALTHCHECK) and
29
+ `docker-compose.yml` (api + RAM-only `redis:8-alpine` with
30
+ persistence disabled). Tag pushes publish
31
+ `ghcr.io/malvavisc0/blitzid` via `.github/workflows/docker.yml`,
32
+ gated on the (now reusable) CI gate plus a compose smoke test that
33
+ submits a real face/ocr/mrz job and polls it to completion.
34
+ - PyPI publishing: tag pushes matching the package version build and
35
+ upload the sdist/wheel to PyPI via `.github/workflows/publish.yml`
36
+ (trusted publishing — no API tokens), gated on the same CI matrix.
37
+ - `DocumentCropper` / `QualityReport` (reading domain, pure CV):
38
+ locates the largest plausible document quad via Canny edges +
39
+ contour approximation, warps it into an axis-aligned canonical crop
40
+ at the quad's own aspect, and quality-checks it (`document_found`,
41
+ `aspect_ratio` near ID-1/ID-3, `resolution`, `sharpness`,
42
+ `brightness`, side-aware `face_present` — never fails a document).
43
+ Optional `FaceDetectorDNN` injection powers the face check
44
+ (`front` + no face → warn; `back` → not expected) without adding a
45
+ runtime `reading → face` dependency; an optional `detector_lock`
46
+ serializes the check against other users of a shared detector. The
47
+ `POST /crop` API endpoint wraps it: QC verdict plus the canonical
48
+ `crop_base64` JPEG.
49
+ - `BLITZID_MODELS_DIR` environment variable: relocates all model-weight
50
+ downloads (SCRFD + RapidOCR) when set; defaults to the platformdirs
51
+ user cache as before. `scripts/download_models.py` pre-fetches all
52
+ weights into a directory (default `models/`) for Docker images.
53
+ - `MRZReader` / `MRZRecord` / `MRZError`: ICAO 9303 machine-readable zone
54
+ reading on top of the `ocr` extra. Selects MRZ lines from OCR output
55
+ (30/36/44 chars over the MRZ charset, reassembled by bbox position),
56
+ validates every check digit plus the letter-only fields (document
57
+ code, issuer, nationality — the ones without check-digit protection),
58
+ and parses TD1, TD2, and TD3 layouts. No fuzzy OCR-error correction:
59
+ an unreadable zone raises `MRZError` with the failing field.
60
+ Document numbers longer than their field follow the ICAO
61
+ convention — no printed check digit, overflow at the start of
62
+ `optional_data1` — and are accepted (the composite check digit
63
+ still covers the full number).
64
+ - `RapidOCRReader` / `OCRText` and the `blitzid[ocr]` extra: OCR text
65
+ reading for ID documents via RapidOCR (PP-OCR ONNX models on
66
+ onnxruntime CPU). Models download on first use to
67
+ `user_cache_dir("blitzid")/models/rapidocr`; `read()` returns text
68
+ lines sorted by confidence. Core installs stay OCR-free.
69
+ `scripts/ocr_demo.py` demonstrates the reader from the CLI.
70
+ - `detect_face_landmarks()`, returning the new frozen `Face` dataclass
71
+ (`bbox`, `confidence`, `landmarks`). SCRFD decodes its 5-point keypoint
72
+ head — right eye, left eye, nose tip, right mouth corner, left mouth
73
+ corner — previously discarded. Existing `detect_face()` tuples are
74
+ unchanged.
75
+ - Positive-path tests on repo image fixtures: the specimen ID card (exactly
76
+ one face — its portrait) and the 1927 conference photo (29 people). Also
77
+ fails fast with `ModelError` on an unexpected SCRFD ONNX output layout.
78
+ - Public `FaceDetectorDNN.cache_enabled` property (scripts no longer read
79
+ the private `_cache` attribute).
80
+ - `face_detector_demo.py` validates `--run` demo names and exits with the
81
+ valid set on typos; cache CLI knobs are documented as custom-preset-only.
82
+ - Detection caching with configurable LRU cache.
83
+ - Multi-scale detection for improved recall on small faces.
84
+ - Typed package (`py.typed` marker).
85
+
86
+ ### Changed
87
+
88
+ - Replaced the SSD ResNet-10 Caffe detector with **SCRFD-2.5G** (InsightFace
89
+ `buffalo_m` ONNX weights) running on **onnxruntime CPU**. OpenCV 5 removed
90
+ the Caffe importer; inference now fails fast with `ModelError` instead of
91
+ falling back to other models or providers.
92
+ - `FaceDetectorDNN` gains a `det_size` parameter (default `640x640`, multiples
93
+ of 32) and letterboxes input images so detections map back to original image
94
+ coordinates without aspect distortion.
95
+ - `DetectionMetrics.backend` now reports `"ONNXRuntime"`.
96
+ - `detect_faces_batch()` now omits failed images from the result dict; an
97
+ empty list always means "processed, zero faces". Failed images are still
98
+ logged. Previously failures were indistinguishable from legitimate
99
+ zero-face results.
100
+ - `DetectionMetrics.faces` now carries `Face` records (landmarks
101
+ accessible) instead of legacy `(x, y, w, h, confidence)` tuples.
102
+ **Breaking** for consumers unpacking metrics entries as tuples.
103
+ - Cache hits report real elapsed cache-lookup time in
104
+ `DetectionMetrics.processing_time` instead of a hardcoded `0.0`.
105
+ - `scales` runs exactly as configured: `_normalize_scales` no longer
106
+ silently injects a `1.0` pass, so `scales=(1.5,)` costs one inference,
107
+ not two.
108
+ - Model downloads stream to a `.tmp` file in 64 KB chunks instead of
109
+ reading the whole file into memory; the socket timeout now bounds the
110
+ transfer.
111
+
112
+ ### Fixed
113
+
114
+ - Model download no longer raises `ModelError` after a successful transfer:
115
+ the post-download size log stat'ed the already-renamed `.tmp` file. Only
116
+ affected fresh installs (cached models never re-downloaded).
117
+ - Detection cache keys now cover image `shape` and `dtype` in addition to
118
+ pixel bytes — arrays sharing byte content but differing in layout no
119
+ longer collide on one cached result.
120
+ - Restored the 10000px `MAX_DIMENSION` guard in image validation, lost in
121
+ the SCRFD migration; oversized inputs fail fast with `ImageError`
122
+ instead of allocating multi-GB arrays.
123
+ - `midv500_face_test.py` face counts now match the crops actually
124
+ considered for saving when `--max-faces-per-image` truncates the list.
125
+
126
+ ### Removed
127
+
128
+ - The DeepFace backend entirely: `FaceDetectorDeepFace`,
129
+ `blitzid[deepface]` extra, the `deepface`/`tf-keras` dependencies,
130
+ `tests/test_deepface.py`, and the `analyze()` API. SCRFD via onnxruntime
131
+ is the single detection backend.
132
+ - CUDA backend support: `require_cuda` parameters, the CUDA/CPU auto-fallback
133
+ chain in `ModelManager.load_network`, the `CUDAConfigError` alias, and
134
+ `scripts/cuda_diagnostics.py`.
135
+ - Dead dataset-annotation helpers in `scripts/midv500_download.py` with zero
136
+ call sites (`list_annotation_paths_recursively`, `calculate_intersect_area`,
137
+ `get_bbox_inside_image`).
138
+ - `FaceDetectorDNN.backend_type` compat alias (superseded by `backend`).
@@ -0,0 +1,39 @@
1
+ # Contributing to BlitzID
2
+
3
+ ## Development setup
4
+
5
+ ```bash
6
+ # Clone and install with dev dependencies
7
+ git clone https://github.com/malvavisc0/blitzid.git
8
+ cd blitzid
9
+ uv sync --extra dev
10
+
11
+ # Install pre-commit hooks
12
+ uv run pre-commit install
13
+ ```
14
+
15
+ ## Running checks locally
16
+
17
+ ```bash
18
+ # Lint
19
+ uv run ruff check src/ tests/ scripts/
20
+ uv run ruff format --check src/ tests/ scripts/
21
+
22
+ # Type checking
23
+ uv run mypy src/ scripts/
24
+
25
+ # Tests
26
+ uv run pytest tests/ -v
27
+
28
+ # Complexity + dead code (see docs/coding-standard.md)
29
+ uvx radon cc src scripts tests -s | grep -E '\-\s(C|D|E|F)\s' # must be empty
30
+ uvx vulture src/ tests/ scripts/ --min-confidence 60
31
+ ```
32
+
33
+ ## Pull request guidelines
34
+
35
+ 1. Fork the repo and create a feature branch from `main`.
36
+ 2. Ensure all checks pass (`ruff`, `mypy`, `pytest`).
37
+ 3. Add or update tests for any new functionality.
38
+ 4. Update `CHANGELOG.md` under `[Unreleased]`.
39
+ 5. Open a PR with a clear description of the change.
@@ -0,0 +1,22 @@
1
+ # ── Builder: install deps and bake model weights ──────────────
2
+ FROM python:3.12-slim AS builder
3
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
4
+ WORKDIR /app
5
+ COPY README.md pyproject.toml uv.lock ./
6
+ COPY src/ src/
7
+ COPY scripts/ scripts/
8
+ RUN uv sync --frozen --no-dev --extra api --extra ocr
9
+ RUN uv run --no-sync python scripts/download_models.py --models-dir /models
10
+
11
+ # ── Runtime: venv + baked weights, non-root ─────────────────────
12
+ FROM python:3.12-slim
13
+ COPY --from=builder /app /app
14
+ COPY --from=builder /models /models
15
+ ENV BLITZID_MODELS_DIR=/models
16
+ ENV PATH=/app/.venv/bin:$PATH
17
+ RUN useradd --create-home appuser
18
+ USER appuser
19
+ EXPOSE 8000
20
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
21
+ CMD ["python", "-c", "import sys, urllib.request; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3).status == 200 else 1)"]
22
+ CMD ["uvicorn", "blitzid.api:app", "--host", "0.0.0.0", "--port", "8000"]
blitzid-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Malvavisc0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.