diff --git a/.githooks/pre-commit b/.githooks/pre-commit index fa4c9e1..29aafd6 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,11 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -RUFF=".venv/bin/ruff" -[[ -x "$RUFF" ]] || RUFF="ruff" +if ! command -v uv >/dev/null 2>&1; then + echo "uv is not installed; run scripts/bootstrap.sh first." >&2 + exit 1 +fi + +# `uv run` auto-syncs missing packages; --all-packages --all-groups keeps +# every workspace member + dev tools in the venv so the hook is self-healing +# after a narrow `uv sync` would otherwise have pruned them. +UV_RUN=(uv run --quiet --all-packages --all-groups) echo "ruff: format check..." -"$RUFF" format --check . +"${UV_RUN[@]}" ruff format --check . echo "ruff: lint..." -"$RUFF" check . +"${UV_RUN[@]}" ruff check . diff --git a/.githooks/pre-push b/.githooks/pre-push index 3dccaaf..59be2ea 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -1,17 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -RUFF=".venv/bin/ruff" -[[ -x "$RUFF" ]] || RUFF="ruff" +if ! command -v uv >/dev/null 2>&1; then + echo "uv is not installed; run scripts/bootstrap.sh first." >&2 + exit 1 +fi -PYTEST=".venv/bin/pytest" -[[ -x "$PYTEST" ]] || PYTEST="pytest" +UV_RUN=(uv run --quiet --all-packages --all-groups) echo "ruff: format check..." -"$RUFF" format --check . +"${UV_RUN[@]}" ruff format --check . echo "ruff: lint..." -"$RUFF" check . +"${UV_RUN[@]}" ruff check . echo "pytest: running tests..." -"$PYTEST" apps +"${UV_RUN[@]}" pytest apps diff --git a/apps/app_admin/requirements.txt b/apps/app_admin/requirements.txt deleted file mode 100644 index 22c6b6d..0000000 --- a/apps/app_admin/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -fastapi==0.115.12 -uvicorn[standard]==0.34.1 diff --git a/apps/app_nippon_rfq_matching/Dockerfile b/apps/app_nippon_rfq_matching/Dockerfile index 3009bba..d8892de 100644 --- a/apps/app_nippon_rfq_matching/Dockerfile +++ b/apps/app_nippon_rfq_matching/Dockerfile @@ -1,70 +1,61 @@ -# Use Debian-based Python image -FROM python:3.12-slim +# ===================================================================== +# uv workspace-sync build. Build context must be the repo root. +# ===================================================================== +FROM python:3.12-slim-bookworm -# Build argument for macOS-specific requirements -ARG INSTALL_MACOS_REQUIREMENTS=false +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + PATH="/app/.venv/bin:$PATH" -# Prevent Python from generating .pyc files -ENV PYTHONDONTWRITEBYTECODE=1 -# Keep Python stdout/stderr from being buffered -ENV PYTHONUNBUFFERED=1 - -# Install system dependencies and uv RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - libpq-dev \ - curl \ - pkg-config \ - libgomp1 \ - libopenblas-dev \ - liblapack-dev \ - libfreetype-dev \ - libpng-dev \ - libjpeg-dev \ - libwebp-dev \ - libtiff-dev \ - libavif-dev \ - && curl -LsSf https://astral.sh/uv/install.sh | sh \ - && apt-get clean \ + build-essential \ + ca-certificates \ + libpq-dev \ + pkg-config \ + libgomp1 \ + libopenblas-dev \ + liblapack-dev \ + libfreetype-dev \ + libpng-dev \ + libjpeg-dev \ + libwebp-dev \ + libtiff-dev \ + libavif-dev \ && rm -rf /var/lib/apt/lists/* -# Add uv to PATH -ENV PATH="/root/.local/bin:${PATH}" - -# Set uv environment variables -ENV UV_COMPILE_BYTECODE=1 \ - UV_LINK_MODE=copy +COPY --from=ghcr.io/astral-sh/uv:0.5 /uv /usr/local/bin/uv -# Set working directory WORKDIR /app -# Create non-root user -RUN groupadd -g 1000 appuser && \ - useradd -r -u 1000 -g appuser appuser - -# Copy requirements first for better caching -COPY requirements.txt . -COPY requirements-macos.txt . +# Workspace manifests first so the dep layer caches independently of source. +COPY pyproject.toml uv.lock* ./ +COPY common/pyproject.toml common/pyproject.toml +COPY apps/app_docling_api/pyproject.toml apps/app_docling_api/pyproject.toml +COPY apps/app_docling_worker/pyproject.toml apps/app_docling_worker/pyproject.toml +COPY apps/app_docling_common/pyproject.toml apps/app_docling_common/pyproject.toml +COPY apps/app_admin/pyproject.toml apps/app_admin/pyproject.toml +COPY apps/app_sample/pyproject.toml apps/app_sample/pyproject.toml +COPY apps/app_nippon_rfq_matching/pyproject.toml apps/app_nippon_rfq_matching/pyproject.toml -# Install Python dependencies using uv -RUN uv pip install --system -r requirements.txt && \ - if [ "$INSTALL_MACOS_REQUIREMENTS" = "true" ]; then uv pip install --system -r requirements-macos.txt; fi +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --no-dev --package app-nippon-rfq-matching --no-install-project -# Copy application code -COPY --chown=appuser:appuser ./app ./app +# Source +COPY common ./common +COPY apps/app_nippon_rfq_matching ./apps/app_nippon_rfq_matching -# Create data directories and set permissions -RUN mkdir -p /app/data/uploads /app/data/storage /app/data/storage/csv /app/data/vectors && \ - chown -R appuser:appuser /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --no-dev --package app-nippon-rfq-matching -# Copy .env.example as .env if .env doesn't exist (for container startup) -COPY --chown=appuser:appuser .env.example .env.example +RUN groupadd -g 1000 appuser \ + && useradd -r -u 1000 -g appuser appuser \ + && mkdir -p /app/data/uploads /app/data/storage/csv /app/data/vectors \ + && chown -R appuser:appuser /app -# Switch to non-root user USER appuser -# Expose port EXPOSE 8000 -# Run the application -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "apps.app_nippon_rfq_matching.app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/apps/app_nippon_rfq_matching/README.md b/apps/app_nippon_rfq_matching/README.md index ac58616..050a79c 100644 --- a/apps/app_nippon_rfq_matching/README.md +++ b/apps/app_nippon_rfq_matching/README.md @@ -37,7 +37,7 @@ rfq_product_matching_nippon_fastapi/ │ └── storage/ # Processed data │ ├── csv/ # CSV exports │ └── models/ # Saved ML models -├── requirements.txt # Python dependencies +├── pyproject.toml # Python dependencies (uv workspace member) └── .env # Environment variables ``` @@ -72,18 +72,13 @@ rfq_product_matching_nippon_fastapi/ ## Installation -1. Create virtual environment: +1. From the repo root, bootstrap the uv workspace: ```bash -python -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate +./scripts/bootstrap.sh +source .venv/bin/activate ``` -2. Install dependencies: -```bash -pip install -r requirements.txt -``` - -3. Set up environment: +2. Set up environment: ```bash cp .env.example .env # Edit .env with your configuration diff --git a/apps/app_nippon_rfq_matching/docker-compose.yml b/apps/app_nippon_rfq_matching/docker-compose.yml index 35e9097..f23d5e0 100644 --- a/apps/app_nippon_rfq_matching/docker-compose.yml +++ b/apps/app_nippon_rfq_matching/docker-compose.yml @@ -3,12 +3,10 @@ version: '3.8' services: api: build: - context: . - dockerfile: Dockerfile - # Set INSTALL_MACOS_REQUIREMENTS=true only on macOS - # On Windows/Linux, leave as false (default) - args: - INSTALL_MACOS_REQUIREMENTS: "false" + # Build context is the repo root because the Dockerfile COPYs workspace + # manifests + common/ + the app's own dir. + context: ../.. + dockerfile: apps/app_nippon_rfq_matching/Dockerfile container_name: rfq-product-matching-api ports: - "8000:8000" diff --git a/apps/app_nippon_rfq_matching/requirements-macos.txt b/apps/app_nippon_rfq_matching/requirements-macos.txt deleted file mode 100644 index 0bc6361..0000000 --- a/apps/app_nippon_rfq_matching/requirements-macos.txt +++ /dev/null @@ -1,151 +0,0 @@ -aiosqlite==0.20.0 -annotated-doc==0.0.4 -annotated-types==0.7.0 -antlr4-python3-runtime==4.9.3 -anyio==4.12.1 -appnope==0.1.4 -asttokens==3.0.1 -attrs==25.4.0 -beautifulsoup4==4.14.3 -certifi==2026.2.25 -cffi==2.0.0 -chardet==7.2.0 -charset-normalizer==3.4.6 -click==8.3.1 -colorlog==6.10.1 -comm==0.2.3 -cryptography==46.0.5 -debugpy==1.8.20 -decorator==5.2.1 -deepsearch-glm==1.0.0 -defusedxml==0.7.1 -dill==0.4.1 -distro==1.9.0 -et-xmlfile==2.0.0 -executing==2.2.1 -faiss-cpu==1.13.2 -faker==40.11.0 -fastapi==0.135.1 -filelock==3.25.2 -filetype==1.2.0 -fsspec==2026.2.0 -h11==0.16.0 -hf-xet==1.4.2 -httpcore==1.0.9 -httptools==0.7.1 -httpx==0.28.1 -idna==3.11 -imageio==2.37.3 -ipykernel==7.2.0 -ipython==9.11.0 -ipython-pygments-lexers==1.1.1 -jedi==0.19.2 -jinja2==3.1.6 -jiter==0.13.0 -joblib==1.5.3 -jsonlines==3.1.0 -jsonref==1.1.0 -jsonschema==4.26.0 -jsonschema-specifications==2025.9.1 -jupyter-client==8.8.0 -jupyter-core==5.9.1 -latex2mathml==3.79.0 -lazy-loader==0.5 -lxml==5.4.0 -markdown-it-py==4.0.0 -marko==2.2.2 -markupsafe==3.0.3 -matplotlib-inline==0.2.1 -mdurl==0.1.2 -mpire==2.10.2 -mpmath==1.3.0 -multiprocess==0.70.19 -nest-asyncio==1.6.0 -networkx==3.6.1 -ninja==1.13.0 -numpy==2.2.0 -omegaconf==2.3.0 -openai==2.29.0 -openpyxl==3.1.5 -packaging==26.0 -pandas==3.0.1 -parso==0.8.6 -pdfminer-six==20251230 -pdfplumber==0.11.9 -pexpect==4.9.0 -pillow==10.4.0 -pip==24.0 -platformdirs==4.9.4 -pluggy==1.6.0 -polyfactory==3.3.0 -prompt-toolkit==3.0.52 -psutil==7.2.2 -ptyprocess==0.7.0 -pure-eval==0.2.3 -pyclipper==1.4.0 -pycparser==3.0 -pydantic==2.12.5 -pydantic-core==2.41.5 -pydantic-settings==2.13.1 -pygments==2.19.2 -pylatexenc==2.10 -pyobjc-core==12.1 -pyobjc-framework-cocoa==12.1 -pyobjc-framework-coreml==12.1 -pyobjc-framework-quartz==12.1 -pyobjc-framework-vision==12.1 -pypdfium2==4.30.0 -python-bidi==0.6.7 -python-dateutil==2.9.0.post0 -python-docx==1.2.0 -python-dotenv==1.0.1 -python-multipart==0.0.12 -python-pptx==1.0.2 -pytz==2026.1.post1 -pyyaml==6.0.3 -pyzmq==27.1.0 -rapidfuzz==3.14.3 -referencing==0.37.0 -regex==2026.2.28 -reportlab==4.2.0 -requests==2.32.5 -rich==14.3.3 -rpds-py==0.30.0 -rtree==1.4.1 -safetensors==0.7.0 -scikit-learn==1.8.0 -scipy==1.17.1 -semchunk==3.2.5 -setuptools==82.0.1 -shapely==2.1.2 -shellingham==1.5.4 -six==1.17.0 -sniffio==1.3.1 -soupsieve==2.8.3 -sqlalchemy==2.0.36 -stack-data==0.6.3 -starlette==0.52.1 -sympy==1.14.0 -tabulate==0.10.0 -threadpoolctl==3.6.0 -tifffile==2026.3.3 -tokenizers==0.22.2 -tornado==6.5.5 -tqdm==4.67.3 -traitlets==5.14.3 -tree-sitter==0.25.2 -tree-sitter-c==0.24.1 -tree-sitter-javascript==0.25.0 -tree-sitter-python==0.25.0 -tree-sitter-typescript==0.23.2 -typer==0.21.2 -typing-extensions==4.15.0 -typing-inspection==0.4.2 -tzdata==2025.3 -urllib3==2.6.3 -uvicorn==0.32.0 -uvloop==0.22.1 -watchfiles==1.1.1 -wcwidth==0.6.0 -websockets==16.0 -xlsxwriter==3.2.9 diff --git a/apps/app_nippon_rfq_matching/requirements.txt b/apps/app_nippon_rfq_matching/requirements.txt deleted file mode 100644 index f9ad528..0000000 --- a/apps/app_nippon_rfq_matching/requirements.txt +++ /dev/null @@ -1,146 +0,0 @@ -aiosqlite==0.20.0 -annotated-doc==0.0.4 -annotated-types==0.7.0 -antlr4-python3-runtime==4.9.3 -anyio==4.12.1 -asttokens==3.0.1 -attrs==25.4.0 -beautifulsoup4==4.14.3 -certifi==2026.2.25 -cffi==2.0.0 -chardet==7.2.0 -charset-normalizer==3.4.6 -click==8.3.1 -colorlog==6.10.1 -comm==0.2.3 -cryptography==46.0.5 -debugpy==1.8.20 -decorator==5.2.1 -deepsearch-glm==1.0.0 -defusedxml==0.7.1 -dill==0.4.1 -distro==1.9.0 -et-xmlfile==2.0.0 -executing==2.2.1 -faiss-cpu==1.13.2 -faker==40.11.0 -fastapi==0.135.1 -filelock==3.25.2 -filetype==1.2.0 -fsspec==2026.2.0 -h11==0.16.0 -hf-xet==1.4.2 -httpcore==1.0.9 -httptools==0.7.1 -httpx==0.28.1 -idna==3.11 -imageio==2.37.3 -ipykernel==7.2.0 -ipython==9.11.0 -ipython-pygments-lexers==1.1.1 -jedi==0.19.2 -jinja2==3.1.6 -jiter==0.13.0 -joblib==1.5.3 -jsonlines==3.1.0 -jsonref==1.1.0 -jsonschema==4.26.0 -jsonschema-specifications==2025.9.1 -jupyter-client==8.8.0 -jupyter-core==5.9.1 -latex2mathml==3.79.0 -lazy-loader==0.5 -lxml==5.4.0 -markdown-it-py==4.0.0 -marko==2.2.2 -markupsafe==3.0.3 -matplotlib-inline==0.2.1 -mdurl==0.1.2 -mpire==2.10.2 -mpmath==1.3.0 -multiprocess==0.70.19 -nest-asyncio==1.6.0 -networkx==3.6.1 -ninja==1.13.0 -numpy==2.2.0 -omegaconf==2.3.0 -openai==2.29.0 -openpyxl==3.1.5 -packaging==26.0 -pandas==3.0.1 -parso==0.8.6 -pdfminer-six==20251230 -pdfplumber==0.11.9 -pexpect==4.9.0 -pillow==10.4.0 -pip==24.0 -platformdirs==4.9.4 -pluggy==1.6.0 -polyfactory==3.3.0 -prompt-toolkit==3.0.52 -psutil==7.2.2 -ptyprocess==0.7.0 -pure-eval==0.2.3 -pyclipper==1.4.0 -pycparser==3.0 -pydantic==2.12.5 -pydantic-core==2.41.5 -pydantic-settings==2.13.1 -pygments==2.19.2 -pylatexenc==2.10 -pypdfium2==4.30.0 -python-bidi==0.6.7 -python-dateutil==2.9.0.post0 -python-docx==1.2.0 -python-dotenv==1.0.1 -python-multipart==0.0.12 -python-pptx==1.0.2 -pytz==2026.1.post1 -pyyaml==6.0.3 -pyzmq==27.1.0 -rapidfuzz==3.14.3 -referencing==0.37.0 -regex==2026.2.28 -reportlab==4.2.0 -requests==2.32.5 -rich==14.3.3 -rpds-py==0.30.0 -rtree==1.4.1 -safetensors==0.7.0 -scikit-learn==1.8.0 -scipy==1.17.1 -semchunk==3.2.5 -setuptools==82.0.1 -shapely==2.1.2 -shellingham==1.5.4 -six==1.17.0 -sniffio==1.3.1 -soupsieve==2.8.3 -sqlalchemy==2.0.36 -stack-data==0.6.3 -starlette==0.52.1 -sympy==1.14.0 -tabulate==0.10.0 -threadpoolctl==3.6.0 -tifffile==2026.3.3 -tokenizers==0.22.2 -tornado==6.5.5 -tqdm==4.67.3 -traitlets==5.14.3 -tree-sitter==0.25.2 -tree-sitter-c==0.24.1 -tree-sitter-javascript==0.25.0 -tree-sitter-python==0.25.0 -tree-sitter-typescript==0.23.2 -typer==0.21.2 -typing-extensions==4.15.0 -typing-inspection==0.4.2 -tzdata==2025.3 -urllib3==2.6.3 -uvicorn==0.32.0 -uvloop==0.22.1 -watchfiles==1.1.1 -wcwidth==0.6.0 -websockets==16.0 -weasyprint==68.1 -xlsxwriter==3.2.9 diff --git a/apps/app_sample/requirements.txt b/apps/app_sample/requirements.txt deleted file mode 100644 index 22c6b6d..0000000 --- a/apps/app_sample/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -fastapi==0.115.12 -uvicorn[standard]==0.34.1 diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 284bcef..191def4 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -6,51 +6,13 @@ PYTHON_VERSION="$(cat "$ROOT_DIR/.python-version")" cd "$ROOT_DIR" -version_matches() { - python3 - "$PYTHON_VERSION" <<'PY' -import re -import sys - -expected = sys.argv[1] -current = sys.version.split()[0] -pattern = re.compile(r"^\d+\.\d+\.\d+$") - -if not pattern.match(expected): - raise SystemExit(1) - -raise SystemExit(0 if current == expected else 1) -PY -} - -if command -v pyenv >/dev/null 2>&1; then - if pyenv versions --bare | grep -Fxq "$PYTHON_VERSION"; then - pyenv local "$PYTHON_VERSION" - elif version_matches; then - echo "python3 already matches $PYTHON_VERSION; skipping pyenv install" - else - echo "Attempting to install Python $PYTHON_VERSION via pyenv..." - if pyenv install -s "$PYTHON_VERSION"; then - pyenv local "$PYTHON_VERSION" - else - echo "pyenv install failed; continuing with the available python3 interpreter" >&2 - fi - fi -fi - -if ! version_matches; then - echo "python3 $(python3 --version 2>&1 | awk '{print $2}') does not match required version $PYTHON_VERSION" >&2 - echo "Install the requested version with pyenv or update .python-version before bootstrapping." >&2 +if ! command -v uv >/dev/null 2>&1; then + echo "uv is not installed. Install it from https://docs.astral.sh/uv/getting-started/installation/ and re-run." >&2 exit 1 fi -python3 -m venv .venv -. .venv/bin/activate -python -m pip install --upgrade pip -pip install -r requirements-dev.txt - -for requirements_file in apps/*/requirements.txt; do - pip install -r "$requirements_file" -done +uv python install "$PYTHON_VERSION" +uv sync --all-packages --all-groups chmod +x .githooks/pre-commit .githooks/pre-push git config core.hooksPath .githooks