Skip to content
Draft

uv #944

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ updates:
- "*"

# Maintain Python dependencies
- package-ecosystem: "pip"
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "monthly"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: "CodeQL Advanced"

on:
pull_request:
types: [opened, edited, ready_for_review, synchronize]
types: [opened, ready_for_review, synchronize]
push:
branches:
- main
Expand Down
24 changes: 16 additions & 8 deletions .github/workflows/lintandformat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ name: Lint and format

on:
pull_request:
types: [opened, edited, ready_for_review, synchronize]
types: [opened, ready_for_review, synchronize]

jobs:
lintandformat:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: ~/.cache/uv

steps:
- name: Check out the code
Expand All @@ -19,20 +22,22 @@ jobs:
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.14"
python-version-file: .python-version

- name: Cache uv folder
id: cache-uv
uses: actions/cache@v4.3.0
with:
path: ~/.cache/uv
key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-uv-${{ hashFiles('requirements.txt', 'requirements-dev.txt') }}
key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-uv-${{ hashFiles('uv.lock') }}

- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
uses: astral-sh/setup-uv@v7
with:
version: "0.9.27"

- name: Install dependencies
run: uv pip install --system -r requirements-dev.txt
run: uv sync --locked

- name: Cache .ruff_cache folder
id: ruff_cache
Expand All @@ -43,8 +48,8 @@ jobs:

- name: Lint and check formating with ruff
run: |
ruff check --output-format=github
ruff format --check
uv run ruff check --output-format=github
uv run ruff format --check

- name: Cache .mypy_cache folder
id: mypy_cache
Expand All @@ -54,4 +59,7 @@ jobs:
key: mypy_cache-${{ github.head_ref }}

- name: Type checking using mypy
run: mypy .
run: uv run mypy .

- name: Minimize uv cache
run: uv cache prune --ci
2 changes: 1 addition & 1 deletion .github/workflows/pr-dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: "PR dependencies"

on:
pull_request:
types: [opened, edited, ready_for_review, synchronize]
types: [opened, ready_for_review, synchronize]

jobs:
pr_dependencies:
Expand Down
24 changes: 16 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
# Configure a constant location for the uv cache
UV_CACHE_DIR: ~/.cache/uv

# Add a Redis container
# https://docs.github.com/en/actions/using-containerized-services/creating-redis-service-containers#running-jobs-directly-on-the-runner-machine
Expand All @@ -20,7 +23,7 @@ jobs:
# Label used to access the service container
redis:
# Docker Hub image
image: redis
image: redis:7.0-alpine3.17
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
Expand All @@ -32,7 +35,7 @@ jobs:
- 6379:6379
postgres:
# Docker Hub image
image: "postgres:15.1"
image: postgres:15.1-alpine3.17
# Provide the password for postgres
env:
POSTGRES_PASSWORD: "somerealpassword"
Expand Down Expand Up @@ -60,20 +63,22 @@ jobs:
uses: actions/setup-python@v6
id: setup-python
with:
python-version: "3.14"
python-version-file: .python-version

- name: Cache uv folder
id: cache-uv
uses: actions/cache@v4.3.0
with:
path: ~/.cache/uv
key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-uv-${{ hashFiles('requirements.txt', 'requirements-dev.txt') }}
key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-uv-${{ hashFiles('uv.lock') }}

- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
uses: astral-sh/setup-uv@v7
with:
version: "0.9.27"

- name: Install dependencies
run: uv pip install --system -r requirements-dev.txt
run: uv sync --locked --all-extras

- name: Cache .pytest_cache folder
id: pytest_cache
Expand All @@ -84,14 +89,17 @@ jobs:

# Run unit tests, run them all when push to main branch
- name: Run all unit tests
run: python tests_script.py --cov --all
run: uv run tests_script.py --cov --all
if: github.event_name == 'push'

- name: Run unit tests for changed files
run: python tests_script.py --cov
run: uv run tests_script.py --cov
if: github.event_name == 'pull_request'

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5.5.1
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Minimize uv cache
run: uv cache prune --ci
62 changes: 38 additions & 24 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
FROM ghcr.io/astral-sh/uv:python3.14-trixie-slim
FROM ghcr.io/astral-sh/uv:0.9.27-python3.14-alpine3.23 AS builder

# Default number of workers; can be overridden at runtime
ENV WORKERS=1
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
ENV UV_NO_DEV=1
ENV UV_PYTHON_DOWNLOADS=0

# Update package list and install weasyprint dependencies
RUN apt-get update && apt-get install -y \
weasyprint \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /hyperion

# Set environment variables to optimize Python behavior in production
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV UV_COMPILE_BYTECODE=1
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project

# First copy only the lockfile to leverage Docker cache
COPY uv.lock .
COPY pyproject.toml .
# Install dependencies using uv with caching
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --all-extras

FROM python:3.14-alpine3.23

# Create non-root user early for better security
# Choose an id that is not likely to be a default one
RUN groupadd --gid 10101 hyperion && \
useradd --uid 10101 --gid hyperion --shell /bin/bash --create-home hyperion
RUN addgroup --system --gid 10101 hyperion
RUN adduser --system --uid 10101 --ingroup hyperion hyperion

WORKDIR /hyperion
# Change ownership of the application directory to the hyperion user
COPY --from=builder --chown=hyperion:hyperion /hyperion /hyperion
ENV PATH="/hyperion/.venv/bin:$PATH"

# Set environment variables to optimize Python behavior in production
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# First copy only the requirements to leverage Docker cache
COPY requirements.txt .
# Default number of workers; can be overridden at runtime
ENV WORKERS=2

# Install weasyprint dependencies
RUN apk add --no-cache weasyprint

# Install dependencies using uv (way faster than pip)
RUN uv pip install --system --no-cache -r requirements.txt
WORKDIR /hyperion

# Then copy the rest of the application code
COPY alembic.ini .
Expand All @@ -33,15 +49,13 @@ COPY assets assets/
COPY migrations migrations/
COPY app app/

# Change ownership of the application directory to the hyperion user
RUN chown -R hyperion:hyperion /hyperion

# Switch to non-root user
USER hyperion

# Expose port 8000
EXPOSE 8000

# Use fastapi cli as the entrypoint
# Use sh -c to allow environment variable expansion
ENTRYPOINT ["sh", "-c", "fastapi run --workers $WORKERS --host 0.0.0.0 --port 8000"]
# Use FastAPI CLI as the entrypoint
# Use shell form to allow environment variable expansion
SHELL ["/bin/sh", "-c"]
ENTRYPOINT fastapi run --workers "$WORKERS" --host "0.0.0.0" --port 8000
6 changes: 5 additions & 1 deletion app/utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
from fastapi import HTTPException, UploadFile
from fastapi.responses import FileResponse
from fastapi.templating import Jinja2Templates
from jellyfish import jaro_winkler_similarity
from jinja2 import Environment, FileSystemLoader, select_autoescape
from pydantic import ValidationError
from sqlalchemy.ext.asyncio import AsyncSession

try:
from jellyfish import jaro_winkler_similarity
except ImportError:
from jellyfish._jellyfish import jaro_winkler_similarity

from app.core.core_endpoints import cruds_core, models_core
from app.core.groups import cruds_groups
from app.core.groups.groups_type import AccountType, GroupType
Expand Down
4 changes: 2 additions & 2 deletions docker-compose-dev.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
hyperion-db:
image: postgres
image: postgres:15.1-alpine3.17
container_name: hyperion-db-dev
restart: unless-stopped
environment:
Expand All @@ -15,7 +15,7 @@ services:
- hyperion_db_data:/var/lib/postgresql/data

hyperion-redis:
image: redis
image: redis:7.0-alpine3.17
container_name: hyperion-redis-dev
restart: unless-stopped
ports:
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
hyperion-db:
image: postgres
image: postgres:15.1-alpine3.17
container_name: hyperion-db
restart: unless-stopped
healthcheck:
Expand All @@ -17,7 +17,7 @@ services:
- ./hyperion_db_data:/var/lib/postgresql/data:Z

hyperion-redis:
image: redis
image: redis:7.0-alpine3.17
container_name: hyperion-redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
Expand Down
64 changes: 64 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,70 @@ license-files = [
"assets/mypayment-terms-of-service.txt",
]

dependencies = [
"aiofiles==24.1.0", # Asynchronous file manipulation
"alembic==1.13.2", # Database migrations
"arq==0.26.3", # Scheduler
"asyncpg==0.31.0", # PostgreSQL adapter for *asynchronous* operations
"authlib==1.6.5",
"bcrypt==4.1.3", # Password hashing
"boto3==1.38.23",
"broadcaster==0.3.1", # Working with websockets with multiple workers
"calypsso==2.7.0",
"faker==37.1.0",
"fastapi[standard]==0.122.0",
"firebase-admin==7.1.0", # For push notification
"google-api-python-client==2.187.0",
"google-auth-oauthlib==1.2.1",
"helloasso-python==1.0.5",
"httpx==0.28.1",
"icalendar==5.0.13",
"jellyfish==1.2.1", # String Matching
"jinja2==3.1.6", # Template engine for HTML files
"phonenumbers==8.13.43", # For phone number validation
"psutil==7.2.1", # To determine the number of Hyperion workers
"psycopg[binary]==3.2.13", # PostgreSQL adapter for *synchronous* operations at startup (database initializations & migrations)
"pydantic==2.12.5",
"pydantic-extra-types==2.10.5",
"pydantic-settings==2.3.4",
"pyjwt[crypto]==2.10.1", # Generate and verify the JWT tokens. Imported as `jwt`
"pymupdf==1.26.7", # PDF processing. Imported as `fitz`
"pypdf==6.4.0",
"python-multipart==0.0.18", # A form data parser, as OAuth2 flow requires form-data parameters
"redis==5.0.8",
"requests==2.32.4",
"sqlalchemy-utils==0.41.2",
"sqlalchemy[asyncio]==2.0.44", # [asyncio] allows greenlet to be installed on Apple M1 devices
"unidecode==1.3.8",
"uvicorn[standard]==0.30.6",
"xlsxwriter==3.2.0",
]

[project.optional-dependencies]
weasyprint = [
"weasyprint==65.1", # HTML to PDF converter
]

[dependency-groups]
dev = [
"aiosqlite==0.20.0",
"boto3-stubs[essential]==1.38.23",
"google-auth-stubs==0.3.0",
"mypy[faster-cache]==1.16.0",
"pytest==9.0.1",
"pytest-alembic==0.12.1",
"pytest-asyncio==1.3.0",
"pytest-cov==6.1.1",
"pytest-mock==3.14.1",
"ruff==0.11.8",
"types-aiofiles==24.1.0.20250516",
"types-authlib==1.5.0.20250516",
"types-fpdf2==2.8.3.20250516",
"types-psutil==7.0.0.20250601",
"types-redis==4.6.0.20241004",
"types-requests==2.32.0.20250515",
]

[tool.titan]
minimal-titan-version-code = 158

Expand Down
17 changes: 0 additions & 17 deletions requirements-dev.txt

This file was deleted.

Loading