tarekmasryo commited on
Commit
c034279
·
verified ·
1 Parent(s): f3c271d

Upload 18 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- Python / caches ---
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .mypy_cache/
8
+ .ipynb_checkpoints/
9
+
10
+ # --- Coverage / test artifacts ---
11
+ .coverage
12
+ htmlcov/
13
+ coverage.xml
14
+
15
+ # --- Packaging / build artifacts ---
16
+ dist/
17
+ build/
18
+ *.egg-info/
19
+
20
+ # --- Git / IDE ---
21
+ .git/
22
+ .vscode/
23
+ .idea/
24
+
25
+ # --- Env / secrets ---
26
+ .env
27
+ .env.*
28
+ .envrc
29
+ .streamlit/secrets.toml
30
+
31
+ # --- OS junk ---
32
+ .DS_Store
33
+ Thumbs.db
34
+
35
+ # --- Logs ---
36
+ *.log
37
+ logs/
38
+
39
+ # --- Runtime artifacts (don’t bake into image) ---
40
+ data/
41
+ datasets/
42
+ artifacts/
43
+ models/
44
+ outputs/
45
+ runs/
46
+
47
+ # --- Node (if any) ---
48
+ node_modules/
.editorconfig ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{yml,yaml}]
12
+ indent_size = 2
13
+
14
+ [*.{json,jsonc}]
15
+ indent_size = 2
16
+
17
+ [*.{js,jsx,ts,tsx}]
18
+ indent_size = 2
19
+
20
+ [Makefile]
21
+ indent_style = tab
22
+
23
+ [*.md]
24
+ trim_trailing_whitespace = false
25
+
26
+ [*.{bat,cmd,ps1}]
27
+ end_of_line = crlf
.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Optional runtime overrides for Docker / local runs
2
+ HOST=0.0.0.0
3
+ PORT=8501
4
+ APP_FILE=app.py
.gitattributes CHANGED
@@ -1,41 +1,17 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
36
- assets/compare-hist-kde-tips.png filter=lfs diff=lfs merge=lfs -text
37
- assets/compare-scatter-flights.png filter=lfs diff=lfs merge=lfs -text
38
- assets/matplotlib-iris-sepal-length-hist.png filter=lfs diff=lfs merge=lfs -text
39
- assets/matplotlib-iris-sepal-length-line.png filter=lfs diff=lfs merge=lfs -text
40
- assets/seaborn-tips-total-bill-hist-sex.png filter=lfs diff=lfs merge=lfs -text
41
- assets/seaborn-tips-total-bill-vs-tip-scatter.png filter=lfs diff=lfs merge=lfs -text
 
1
+ * text=auto eol=lf
2
+
3
+ *.bat text eol=crlf
4
+ *.cmd text eol=crlf
5
+ *.ps1 text eol=crlf
6
+
7
+ # binaries (do not normalize / no eol)
8
+ *.png binary
9
+ *.jpg binary
10
+ *.jpeg binary
11
+ *.gif binary
12
+ *.webp binary
13
+ *.ico binary
14
+ *.pdf binary
15
+ *.zip binary
16
+ *.gz binary
17
+ *.7z binary
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/dependabot.yml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: 2
2
+
3
+ updates:
4
+ - package-ecosystem: "pip"
5
+ directory: "/"
6
+ target-branch: "main"
7
+ schedule:
8
+ interval: "weekly"
9
+ day: "monday"
10
+ time: "09:00"
11
+ timezone: "Africa/Cairo"
12
+ open-pull-requests-limit: 2
13
+ rebase-strategy: "auto"
14
+ labels: ["dependencies", "python"]
15
+ groups:
16
+ python-patch-minor:
17
+ patterns: ["*"]
18
+ update-types: ["patch", "minor"]
19
+ python-major:
20
+ patterns: ["*"]
21
+ update-types: ["major"]
22
+ # Optional safety: keep major updates manual for heavy deps
23
+ # ignore:
24
+ # - dependency-name: "numpy"
25
+ # update-types: ["version-update:semver-major"]
26
+ # - dependency-name: "pandas"
27
+ # update-types: ["version-update:semver-major"]
28
+ # - dependency-name: "scikit-learn"
29
+ # update-types: ["version-update:semver-major"]
30
+
31
+ - package-ecosystem: "github-actions"
32
+ directory: "/"
33
+ target-branch: "main"
34
+ schedule:
35
+ interval: "weekly"
36
+ day: "monday"
37
+ time: "09:00"
38
+ timezone: "Africa/Cairo"
39
+ open-pull-requests-limit: 2
40
+ rebase-strategy: "auto"
41
+ labels: ["dependencies", "github-actions"]
42
+ groups:
43
+ actions-patch-minor:
44
+ patterns: ["*"]
45
+ update-types: ["patch", "minor"]
46
+ actions-major:
47
+ patterns: ["*"]
48
+ update-types: ["major"]
.github/workflows/ci.yml ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ci-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ ci:
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 15
19
+
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.11"]
24
+
25
+ steps:
26
+ - name: Checkout
27
+ uses: actions/checkout@v6
28
+
29
+ - name: Setup Python
30
+ uses: actions/setup-python@v6
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ cache: "pip"
34
+ cache-dependency-path: |
35
+ requirements.txt
36
+ requirements-dev.txt
37
+
38
+ - name: Install dependencies
39
+ run: |
40
+ python -m pip install -U pip
41
+ python -m pip install -r requirements-dev.txt
42
+
43
+ - name: Basic sanity (compile)
44
+ run: python -m compileall -q .
45
+
46
+ - name: Ruff (lint + format check)
47
+ run: |
48
+ python -m ruff check .
49
+ python -m ruff format --check .
50
+
51
+ - name: Pytest
52
+ run: python -m pytest -q
53
+
54
+ - name: Docker present?
55
+ id: dockerfile
56
+ shell: bash
57
+ run: |
58
+ if [ -f Dockerfile ]; then echo "present=true" >> "$GITHUB_OUTPUT"; else echo "present=false" >> "$GITHUB_OUTPUT"; fi
59
+
60
+ - name: Set Docker image name (lowercase)
61
+ if: steps.dockerfile.outputs.present == 'true'
62
+ shell: bash
63
+ run: echo "IMAGE_NAME=$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
64
+
65
+ - name: Set up Docker Buildx
66
+ if: steps.dockerfile.outputs.present == 'true'
67
+ uses: docker/setup-buildx-action@v3
68
+
69
+ - name: Docker test (build test stage)
70
+ if: steps.dockerfile.outputs.present == 'true'
71
+ uses: docker/build-push-action@v6
72
+ with:
73
+ context: .
74
+ target: test
75
+ tags: ${{ env.IMAGE_NAME }}:test
76
+ cache-from: type=gha
77
+ cache-to: type=gha,mode=max
78
+
79
+ - name: Docker build (runtime)
80
+ if: steps.dockerfile.outputs.present == 'true'
81
+ uses: docker/build-push-action@v6
82
+ with:
83
+ context: .
84
+ target: runtime
85
+ tags: ${{ env.IMAGE_NAME }}:ci
86
+ cache-from: type=gha
87
+ cache-to: type=gha,mode=max
.gitignore ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =========================
2
+ # Python
3
+ # =========================
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.pyd
7
+ *.egg-info/
8
+
9
+ # Packaging / build
10
+ dist/
11
+ build/
12
+ pip-wheel-metadata/
13
+
14
+ # Virtual environments
15
+ .venv/
16
+ venv/
17
+ ENV/
18
+ .venv.bak/
19
+ venv.bak/
20
+
21
+ # Environment variables / secrets
22
+ .env
23
+ .env.*
24
+ !.env.example
25
+
26
+ # Caches
27
+ .pytest_cache/
28
+ .ruff_cache/
29
+ .mypy_cache/
30
+ .pytype/
31
+ .pyre/
32
+
33
+ # =========================
34
+ # Tests / coverage
35
+ # =========================
36
+ .coverage
37
+ coverage.xml
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ *.cover
42
+ *.py,cover
43
+
44
+ # =========================
45
+ # Jupyter / notebooks
46
+ # =========================
47
+ .ipynb_checkpoints/
48
+ *.ipynb~
49
+
50
+ # =========================
51
+ # Streamlit (keep config, ignore secrets)
52
+ # =========================
53
+ .streamlit/secrets.toml
54
+
55
+ # Logs
56
+ *.log
57
+ logs/
58
+
59
+ # Local databases (optional but common)
60
+ *.db
61
+ *.sqlite
62
+ *.sqlite3
63
+
64
+ # =========================
65
+ # OS / IDE
66
+ # =========================
67
+ .DS_Store
68
+ Thumbs.db
69
+ .vscode/
70
+ .idea/
71
+ *.iml
72
+ .history/
73
+
74
+ # =========================
75
+ # Git
76
+ # =========================
77
+ *.orig
.pre-commit-config.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_language_version:
2
+ python: python3.11
3
+
4
+ repos:
5
+ - repo: https://github.com/astral-sh/ruff-pre-commit
6
+ rev: v0.14.10
7
+ hooks:
8
+ - id: ruff-check
9
+ args: [--fix]
10
+ - id: ruff-format
11
+
12
+ - repo: https://github.com/pre-commit/pre-commit-hooks
13
+ rev: v4.6.0
14
+ hooks:
15
+ - id: check-yaml
16
+ - id: end-of-file-fixer
17
+ - id: trailing-whitespace
18
+ - id: check-merge-conflict
19
+ - id: detect-private-key
20
+ - id: check-added-large-files
21
+ args: ["--maxkb=10240"]
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.11.9
CONTRIBUTING.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing
2
+
3
+ This is a portfolio project. Issues and suggestions are welcome.
4
+
5
+ ## Ways to contribute
6
+ - Report bugs (include steps to reproduce and logs).
7
+ - Suggest improvements (UX, performance, reliability).
8
+ - Propose small PRs (docs, tests, refactors).
9
+ - Security issues: please avoid posting sensitive details publicly.
10
+
11
+ ## Dev setup
12
+
13
+ From the repo root (inside a virtual environment):
14
+
15
+ ```bash
16
+ python -m pip install -r requirements.txt
17
+ python -m pip install -r requirements-dev.txt
18
+ python -m ruff check .
19
+ python -m ruff format --check .
20
+ python -m pytest -q
21
+ python -m streamlit run app.py
Makefile ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .PHONY: help install dev run lint lint-fix format test check precommit
2
+
3
+ PY ?= python
4
+ APP_FILE ?= app.py
5
+
6
+ help:
7
+ @echo "Targets:"
8
+ @echo " install Install runtime deps (requirements.txt if present)"
9
+ @echo " dev Install dev deps (requirements-dev.txt if present)"
10
+ @echo " run Run app (Streamlit by default)"
11
+ @echo " lint Run ruff lint"
12
+ @echo " lint-fix Run ruff lint with fixes"
13
+ @echo " format Format code with ruff"
14
+ @echo " check Lint + format check + tests"
15
+ @echo " test Run pytest"
16
+ @echo " precommit Install pre-commit hooks"
17
+
18
+ install:
19
+ $(PY) -m pip install -U pip
20
+ ifneq ($(wildcard requirements.txt),)
21
+ $(PY) -m pip install -r requirements.txt
22
+ endif
23
+ @$(PY) -m pip check || true
24
+
25
+ dev:
26
+ $(PY) -m pip install -U pip
27
+ ifneq ($(wildcard requirements-dev.txt),)
28
+ $(PY) -m pip install -r requirements-dev.txt
29
+ endif
30
+ @$(PY) -m pip check || true
31
+
32
+ run:
33
+ $(PY) -m streamlit run $(APP_FILE)
34
+
35
+ lint:
36
+ $(PY) -m ruff check .
37
+
38
+ lint-fix:
39
+ $(PY) -m ruff check . --fix --exit-non-zero-on-fix
40
+
41
+ format:
42
+ $(PY) -m ruff format .
43
+
44
+ test:
45
+ $(PY) -m pytest -q
46
+
47
+ check: lint
48
+ $(PY) -m ruff format --check .
49
+ $(PY) -m pytest -q
50
+
51
+ precommit:
52
+ $(PY) -m pip install -U pre-commit
53
+ pre-commit install
SECURITY.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Please do **not** create a public GitHub issue for security vulnerabilities.
6
+
7
+ Instead, use GitHub's private reporting flow:
8
+
9
+ 1. Go to this repository → **Security** tab
10
+ 2. Open **Advisories**
11
+ 3. Click **New draft security advisory**
12
+ 4. Include:
13
+ - a short description of the issue
14
+ - steps to reproduce (proof-of-concept if safe)
15
+ - affected versions/commits (if known)
16
+ - impact assessment (what an attacker can do)
17
+
18
+ We will respond as soon as possible and coordinate a responsible disclosure.
pyproject.toml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.ruff]
2
+ line-length = 100
3
+ target-version = "py311"
4
+ extend-exclude = [
5
+ ".venv",
6
+ "venv",
7
+ "__pycache__",
8
+ ".pytest_cache",
9
+ ".ruff_cache",
10
+ ".mypy_cache",
11
+ "build",
12
+ "dist",
13
+ "artifacts",
14
+ "models",
15
+ "outputs",
16
+ "runs",
17
+ "data",
18
+ "datasets",
19
+ ]
20
+
21
+ [tool.ruff.lint]
22
+ select = ["E", "F", "I", "B", "UP"]
23
+ ignore = ["E501"]
24
+
25
+ [tool.ruff.lint.isort]
26
+ known-first-party = []
27
+ combine-as-imports = true
28
+
29
+ [tool.ruff.lint.per-file-ignores]
30
+ "tests/**.py" = ["S101"] # allow assert in tests (safe)
31
+
32
+ [tool.ruff.format]
33
+ quote-style = "double"
34
+ indent-style = "space"
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
38
+ addopts = "-q"
requirements-dev.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ -r requirements.txt
2
+
3
+ ruff==0.14.11
4
+ pytest==8.3.4
5
+ pre-commit==4.5.1
requirements.txt CHANGED
@@ -1,6 +1,11 @@
1
- streamlit>=1.35
2
- pandas>=2.0
3
- numpy>=1.24
4
- matplotlib>=3.7
5
- seaborn>=0.13
6
- scipy>=1.10
 
 
 
 
 
 
1
+ streamlit==1.52.2
2
+ pandas==2.3.3
3
+ numpy==2.4.1
4
+ scikit-learn==1.8.0
5
+ scipy==1.17.0
6
+ plotly==5.24.0
7
+ joblib==1.5.3
8
+
9
+
10
+ matplotlib>=3.8,<4
11
+ seaborn>=0.13,<0.14
scripts/doctor.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # scripts/doctor.py
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import platform
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).resolve().parents[1]
12
+
13
+
14
+ def run(cmd: list[str]) -> int:
15
+ return subprocess.call(cmd, cwd=str(ROOT))
16
+
17
+
18
+ def has_file(name: str) -> bool:
19
+ return (ROOT / name).exists()
20
+
21
+
22
+ def which(exe: str) -> str | None:
23
+ return shutil.which(exe)
24
+
25
+
26
+ def banner(title: str) -> None:
27
+ print("\n" + "=" * 72)
28
+ print(title)
29
+ print("=" * 72)
30
+
31
+
32
+ def info(k: str, v: str) -> None:
33
+ print(f"{k:<24} {v}")
34
+
35
+
36
+ def warn(msg: str) -> None:
37
+ print(f"[WARN] {msg}")
38
+
39
+
40
+ def fail(msg: str) -> None:
41
+ print(f"[FAIL] {msg}")
42
+
43
+
44
+ def ok(msg: str) -> None:
45
+ print(f"[OK] {msg}")
46
+
47
+
48
+ def parse_min_python() -> tuple[int, int]:
49
+ # Default policy for the kit; override via env if needed.
50
+ v = os.getenv("MIN_PYTHON", "3.11").strip()
51
+ parts = v.split(".")
52
+ return int(parts[0]), int(parts[1])
53
+
54
+
55
+ def check_python_version() -> bool:
56
+ min_major, min_minor = parse_min_python()
57
+ if sys.version_info >= (min_major, min_minor):
58
+ ok(f"Python version >= {min_major}.{min_minor} ({sys.version.split()[0]})")
59
+ return True
60
+ fail(f"Python too old: {sys.version.split()[0]} < {min_major}.{min_minor}")
61
+ return False
62
+
63
+
64
+ def check_venv() -> None:
65
+ in_venv = sys.prefix != sys.base_prefix
66
+ info("In venv", str(in_venv))
67
+ if not in_venv:
68
+ warn("Not in a virtualenv. Recommended: create .venv and activate it.")
69
+
70
+
71
+ def suggest_install() -> None:
72
+ banner("Suggested setup commands")
73
+ py = sys.executable
74
+ if has_file("requirements-dev.txt"):
75
+ print(f"{py} -m pip install -U pip")
76
+ print(f"{py} -m pip install -r requirements-dev.txt")
77
+ elif has_file("requirements.txt"):
78
+ print(f"{py} -m pip install -U pip")
79
+ print(f"{py} -m pip install -r requirements.txt")
80
+ warn("requirements-dev.txt not found (dev tooling may be missing).")
81
+ else:
82
+ warn("No requirements*.txt found.")
83
+
84
+
85
+ def check_tools() -> None:
86
+ banner("Tooling")
87
+ py = sys.executable
88
+ # Try importing these only if installed; don't crash the script.
89
+ for mod in ["ruff", "pytest"]:
90
+ code = run([py, "-c", f"import {mod}"])
91
+ if code == 0:
92
+ ok(f"import {mod}")
93
+ else:
94
+ warn(f"{mod} not importable (install dev deps).")
95
+
96
+ pc = which("pre-commit")
97
+ if pc:
98
+ ok("pre-commit found in PATH")
99
+ else:
100
+ warn("pre-commit not found in PATH (optional, but recommended).")
101
+
102
+
103
+ def run_checks() -> int:
104
+ banner("Running checks")
105
+ py = sys.executable
106
+ steps = [
107
+ ([py, "-m", "ruff", "check", "."], "ruff check"),
108
+ ([py, "-m", "ruff", "format", "--check", "."], "ruff format --check"),
109
+ ([py, "-m", "pytest", "-q"], "pytest"),
110
+ ]
111
+ rc = 0
112
+ for cmd, name in steps:
113
+ print(f"\n$ {' '.join(cmd)}")
114
+ code = run(cmd)
115
+ if code != 0:
116
+ fail(f"{name} failed (exit={code})")
117
+ rc = code
118
+ break
119
+ ok(f"{name} passed")
120
+ return rc
121
+
122
+
123
+ def main() -> int:
124
+ banner("Doctor")
125
+ info("OS", f"{platform.system()} {platform.release()}")
126
+ info("Python", sys.version.splitlines()[0])
127
+ info("Repo", str(ROOT))
128
+
129
+ py_ok = check_python_version()
130
+ check_venv()
131
+
132
+ banner("Project files")
133
+ for f in ["pyproject.toml", "requirements.txt", "requirements-dev.txt", "Dockerfile"]:
134
+ info(f, "present" if has_file(f) else "missing")
135
+
136
+ check_tools()
137
+ suggest_install()
138
+
139
+ if not py_ok:
140
+ return 2
141
+
142
+ if "--check" in sys.argv:
143
+ return run_checks()
144
+
145
+ print("\nTip: run `python scripts/doctor.py --check`")
146
+ return 0
147
+
148
+
149
+ if __name__ == "__main__":
150
+ raise SystemExit(main())
tests/__pycache__/test_smoke.cpython-311-pytest-8.3.4.pyc ADDED
Binary file (4.9 kB). View file
 
tests/test_smoke.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib
2
+ import importlib.util
3
+ import os
4
+ import sys
5
+
6
+
7
+ def _parse_version(v: str) -> tuple[int, int]:
8
+ parts = v.strip().split(".")
9
+ if len(parts) < 2:
10
+ raise ValueError("MIN_PYTHON must be like '3.10' or '3.11'")
11
+ return int(parts[0]), int(parts[1])
12
+
13
+
14
+ def test_smoke_python_version():
15
+ min_py = os.getenv("MIN_PYTHON", "3.11")
16
+ assert sys.version_info >= _parse_version(min_py)
17
+
18
+
19
+ def test_smoke_core_imports():
20
+ """
21
+ Fast fail if core dependencies are broken.
22
+
23
+ Configure via:
24
+ SMOKE_IMPORTS="numpy,pandas,sklearn,streamlit,plotly"
25
+ """
26
+ imports = os.getenv("SMOKE_IMPORTS", "")
27
+ if not imports.strip():
28
+ return
29
+
30
+ for mod in [m.strip() for m in imports.split(",") if m.strip()]:
31
+ importlib.import_module(mod)
32
+
33
+
34
+ def test_smoke_project_module_importable():
35
+ """
36
+ Ensure the main project module imports.
37
+ Configure via:
38
+ PROJECT_MODULE="your_package_name"
39
+ """
40
+ module = os.getenv("PROJECT_MODULE", "").strip()
41
+ if not module:
42
+ return
43
+ importlib.import_module(module)
44
+
45
+
46
+ def test_smoke_app_importable():
47
+ """
48
+ Importing the app module should not execute heavy work at import-time.
49
+
50
+ This kit doesn't ship `app.py` because it varies per project. So we:
51
+ - Try to import the module if present
52
+ - Otherwise, skip silently (new project will add app.py later)
53
+
54
+ Override via:
55
+ APP_MODULE="app" (default)
56
+ """
57
+ app_module = os.getenv("APP_MODULE", "app").strip()
58
+
59
+ # If the module isn't present yet, skip.
60
+ if importlib.util.find_spec(app_module) is None:
61
+ return
62
+
63
+ importlib.import_module(app_module)