37 Commits
Author SHA1 Message Date
herel 640bbea802 fix: specific redirect codes for mobile 2026-07-06 14:14:02 +02:00
herel 7ae137f865 feat: Implement Flutter mobile app MVP
Complete Flutter mobile app structure with:
- All data models (Account, Event, Player, Staff, Convocation, EventDetail, UserInfo)
- ApiService with Dio and Bearer token interceptor
- AuthService with OIDC via flutter_web_auth and deep links
- AuthProvider, ScheduleProvider, EventDetailProvider state management
- LoginScreen, ScheduleScreen with filters, EventDetailScreen
- Reusable widgets: EventCard, FilterDropdown, LoadingIndicator
- main.dart with MultiProvider and auth initialization
- AndroidManifest.xml and Info.plist deep link configuration for myice://callback

Connects to existing FastAPI backend for schedule, events, and auth.
2026-07-06 13:31:49 +02:00
herel cabd121ac1 docs: Add Flutter app design specification
Add the full design specification for the MyIce Flutter mobile app.
The app connects to the existing FastAPI backend and replicates
the webapp functionality with OIDC auth, schedule browsing,
and event detail views.
2026-07-06 13:25:20 +02:00
herel dbbe2171c2 chore: update .gitignore 2026-06-29 23:11:51 +02:00
herel 09952e4dc1 fix: remove Cross-Account Data Leakage 2026-06-29 23:04:48 +02:00
herel c7990b0971 chore: remove debugging prints 2026-06-29 23:03:48 +02:00
herel a17c362c81 fix(frontend): Sanitize DOM rendering to prevent XSS via innerHTML
Replace unsafe innerHTML template literals with document.createElement()
and textContent for all user-conrolled data fields in index.html.

Event cards and modal details previously interpolated API responses
(event.name, event.title, player.fname, staff.role, etc.) directly into
innerHTML without escaping, allowing stored XSS if upstream MyIce data
is malicious (CWE-79 / vuln-0002).

All dynamic text now flows through textContent or createTextNode(),
while static HTML structures (SVG icons, strong labels) remain safe
hardcoded innerHTML assignments.

Refs vuln-0002
2026-06-29 20:05:45 +02:00
herel 77b2531106 fix(api): Send PKCE code_verifier in token exchange and secure OIDC cookies
The /exchange-token endpoint was not sending the code_verifier to the
token endpoint, rendering PKCE protection ineffective. Add the verifier
to the POST body so the IdP can validate the authorization code exchange.

Also add secure=True and samesite='Lax' to all OIDC cookies on /login
to improve cookie security per the recommended remediation.

Refs vuln-0004
2026-06-29 19:49:07 +02:00
herel 0d39efd034 fix: use tailwindcss cli 2026-06-29 18:02:10 +02:00
herel 12aada4986 fix: better subgroup 2026-06-29 17:35:48 +02:00
herel 9795a4c813 fix: proper jwt verification 2026-06-29 17:35:36 +02:00
herel 46ae216e97 fix: IDC State/Nonce Not Validated 2026-06-29 16:53:14 +02:00
herel 19724b1f8b fix: add timeout for request 2026-06-29 16:52:35 +02:00
herel 9e5db51972 fix: Weak JWT Validation 2026-06-29 16:48:02 +02:00
herel c0bfa85688 fix: No static key fallback; reject non-OIDC tokens 2026-06-29 16:43:09 +02:00
herel ffe0a1cf18 fix: remove hard coded old secret 2026-06-29 16:42:51 +02:00
herel ff69806803 fix: Permissive CORS Configuration 2026-06-29 16:40:36 +02:00
herel 524c4db889 fix: Missing Authentication Checks 2026-06-29 16:37:39 +02:00
herel 6b2965c18a fix(security): Authentication Bypass via Broken JWT Validation in AuthHeaders.validate_oidc_token 2026-06-29 16:33:21 +02:00
herel 9363a1f7ef chore: add some debug 2026-06-29 16:30:35 +02:00
herel 449f5450fe chore: update categories 2026-06-29 16:28:15 +02:00
herel f302357545 fix: prevent OS Command Injection 2026-06-29 16:19:33 +02:00
herel b1a792502f fix(schedule): Use midnight as schedule start date
Change date_start from current time to today at midnight so the
schedule fetch query properly covers the full day.
2026-06-05 15:12:33 +02:00
herel 38e3564524 chore: update Dockerfile 2025-11-11 11:06:40 +01:00
herel 6a240c5773 feat: add ESC key support to close event details modal and bump version to v0.6.0 2025-11-11 08:51:58 +01:00
herel b04e8e2900 docs: improve AGENTS.md with better build/lint/test commands and code style guidelines 2025-11-11 08:46:13 +01:00
herel f33bb46d73 chore(docker): use docker cache mount for pip install step 2025-10-15 16:03:01 +02:00
herel cd32efb657 feat: redesign UI with Tailwind CSS and improve event display 2025-10-15 15:59:57 +02:00
herel 747e9db582 chore: use python image 3.13-slim instead of 3.13-alpine 2025-10-01 16:22:48 +02:00
herel 768ac8375b feat: slim down Docker image size by 8x using Alpine Linux base and multi-stage build 2025-09-30 09:22:26 +02:00
herel 4a5b2a5991 chore: update fastapi and dependencies to latest versions 2025-09-30 08:37:56 +02:00
herel c099f1328b chore: migrate to python 3.13 and update dependencies
Migrate from Python 3.11 to 3.13 with updated dependencies. Switch from PyPDF2 to pypdf library for better PDF processing. Add new U14 age groups and extract-pdf utility script.
2025-09-29 23:05:48 +02:00
herel fe8a4e5d09 refactor: migrate to distroless multi-stage Docker build 2025-09-29 22:07:11 +02:00
herel 781d7000cd feat: display staff information and improve empty state handling in game details modal 2025-09-29 20:17:23 +02:00
herel 6903dab06b doc: fix markdownlint issues in AGENTS.md 2025-09-23 09:33:44 +02:00
herel abfad23a7a chore: bump version to v0.5.7 2025-08-20 11:10:20 +02:00
herel 68ecce7d3a feat: layout filters horizontally 2025-08-20 11:10:06 +02:00
24 changed files with 5367 additions and 1264 deletions

No files matched your search

+6 -1
View File
@@ -3,7 +3,11 @@ myice.ini
schedule.json schedule.json
.envrc .envrc
cookies.txt cookies.txt
# Byte-compiled / optimized / DLL files # Tailwind CSS generated artifacts
style.css
tailwindcss
# Python bytecode
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
@@ -165,3 +169,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
strix_runs/*
+1
View File
@@ -0,0 +1 @@
docs/superpowers/specs/*.md
+2 -2
View File
@@ -29,7 +29,7 @@ repos:
- id: mypy - id: mypy
exclude: ^(docs/|example-plugin/) exclude: ^(docs/|example-plugin/)
args: [--ignore-missing-imports] args: [--ignore-missing-imports]
additional_dependencies: [types-requests, PyPDF2] additional_dependencies: [types-requests, pypdf]
- repo: https://github.com/adrienverge/yamllint.git - repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1 rev: v1.37.1
hooks: hooks:
@@ -40,7 +40,7 @@ repos:
rev: v0.13.0 rev: v0.13.0
hooks: hooks:
- id: markdownlint - id: markdownlint
exclude: "^.github|(^docs/_sidebar\\.md$)" exclude: "^.github|_sidebar\\.md$|superpowers/(specs|plans)/.*\\.md$"
- repo: https://github.com/shellcheck-py/shellcheck-py - repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1 rev: v0.11.0.1
+63 -4
View File
@@ -26,11 +26,51 @@ yamllint . # YAML linting
markdownlint . # Markdown linting markdownlint . # Markdown linting
``` ```
### Testing ### Running Tests
```bash ```bash
# Run tests (no specific test framework configured) # Manual testing approach - run individual commands to test functionality
# Project uses manual testing with example PDF files in repository # Test schedule fetching
myice schedule --days 7
# Test mobile login
myice mobile-login
# Test specific command help
myice search --help
# Test web API (run in background)
poetry run fastapi run myice/webapi.py &
# Run a single pre-commit hook on specific file
pre-commit run ruff --files myice/myice.py
pre-commit run ruff-format --files myice/webapi.py
pre-commit run mypy --files myice/myice.py
```
### CSS/Frontend Build
When modifying `index.html` or the Tailwind CSS setup:
```bash
# One-off build of style.css
./build-css.sh
# Watch mode for development
./build-css.sh --watch
# Or manually with the standalone CLI (if already downloaded)
./tailwindcss -i ./style-input.css -o ./style.css --minify
```
### Running the Web API
```bash
# Run with poetry
poetry run fastapi run myice/webapi.py --host 127.0.0.1
# Run with uv (alternative)
uv run fastapi run myice/webapi.py --host 127.0.0.1
``` ```
## Code Style Guidelines ## Code Style Guidelines
@@ -73,5 +113,24 @@ markdownlint . # Markdown linting
- Typer for CLI interface - Typer for CLI interface
- FastAPI for web API - FastAPI for web API
- requests for HTTP requests - requests for HTTP requests
- PyPDF2 for PDF processing - pypdf for PDF processing
- Use rich for enhanced console output - Use rich for enhanced console output
- Custom rl_ai_tools package for AI functionalities
### Git Commit Messages
- Use conventional commits format
- Never mention Claude in commit messages
- Be descriptive but concise
- Use present tense ("add feature" not "added feature")
### Additional Rules
- Always use ddg-mcp to perform Web Search functionality
- Follow the existing code patterns in myice/myice.py and myice/webapi.py
- Maintain backward compatibility when modifying existing APIs
- Document new features in README.md
- Always run ruff format and ruff check after editing a python file
- Use conventional commit messages
- When fixing JSON parsing issues, follow the pattern established in
sanitize_json_response function
+51 -10
View File
@@ -1,20 +1,61 @@
FROM python:3.11 # Multi-stage build to create a minimal image
FROM python:3.13-slim AS builder
RUN install -o www-data -g www-data -d -m 0755 /var/www # Create working directory
WORKDIR /app
USER www-data # Install tools needed for Tailwind CSS build
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
&& rm -rf /var/lib/apt/lists/*
RUN curl -sSL https://install.python-poetry.org | python3 - # Copy dependency files
COPY requirements.txt ./
ENV PATH=/var/www/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # Install dependencies to a target directory
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-deps --disable-pip-version-check -r requirements.txt
COPY README.md pyproject.toml poetry.lock docker-entrypoint.sh index.html favicon.ico /var/www/ # Build Tailwind CSS
COPY myice /var/www/myice COPY index.html style-input.css tailwind.config.js ./
RUN curl -sL https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.17/tailwindcss-linux-x64 -o tailwindcss \
&& chmod +x tailwindcss \
&& ./tailwindcss -i ./style-input.css -o ./style.css --minify
WORKDIR /var/www # Runtime stage
FROM python:3.13-slim AS runtime
RUN poetry install && . $(poetry env info -p) # Create working directory
WORKDIR /app
# Install only runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Create a non-root user for security
RUN useradd --home-dir /app --no-create-home --uid 1000 myice
# Copy installed packages from builder stage
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
# Copy application code and compiled assets
COPY index.html favicon.ico ./
COPY --from=builder /app/style.css ./style.css
COPY myice ./myice
# Change ownership of copied files
RUN chown -R myice:myice /app
# Switch to non-root user
USER myice
# Bytecompile Python files for faster first load
RUN python -m compileall -q ./myice
# Expose port
EXPOSE 8000 EXPOSE 8000
ENTRYPOINT [ "/var/www/docker-entrypoint.sh" ] # Run the application
ENTRYPOINT ["python", "-m", "uvicorn", "myice.webapi:app", "--host", "0.0.0.0", "--port", "8000"]
+23 -9
View File
@@ -10,13 +10,17 @@ the PDFs you need.
with [uv](https://docs.astral.sh/uv/getting-started/installation/): with [uv](https://docs.astral.sh/uv/getting-started/installation/):
```shell ```shell
uv tool install --extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ myice uv tool install \
--extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ \
myice
``` ```
with [pipx](https://pipx.pypa.io/stable/installation/): with [pipx](https://pipx.pypa.io/stable/installation/):
```shell ```shell
pipx install --extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ myice pipx install \
--extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ \
myice
``` ```
## configuration ## configuration
@@ -80,9 +84,11 @@ Then open your browser at `http://localhost:8000`. The web interface allows you
The web interface supports two authentication methods: The web interface supports two authentication methods:
1. **Infomaniak OpenID Connect (Recommended)**: Click the "Se connecter avec Infomaniak" button to authenticate using Infomaniak's OIDC provider. Only users in the allowed list will be granted access. 1. **Infomaniak OpenID Connect (Recommended)**: Click the "Se connecter avec
Infomaniak" button to authenticate using Infomaniak's OIDC provider. Only
users in the allowed list will be granted access.
2. **Static API Key**: For development purposes, you can still use `abc` as the token. 1. **Static API Key**: For development purposes, you can still use `abc` as the token.
### Environment Variables ### Environment Variables
@@ -90,8 +96,8 @@ To configure OIDC authentication, set the following environment variables:
- `CLIENT_ID`: Your OIDC client ID (default: 8ea04fbb-4237-4b1d-a895-0b3575a3af3f) - `CLIENT_ID`: Your OIDC client ID (default: 8ea04fbb-4237-4b1d-a895-0b3575a3af3f)
- `CLIENT_SECRET`: Your OIDC client secret - `CLIENT_SECRET`: Your OIDC client secret
- `REDIRECT_URI`: The redirect URI (default: http://localhost:8000/callback) - `REDIRECT_URI`: The redirect URI (default: `http://localhost:8000/callback`)
- `ALLOWED_USERS`: Comma-separated list of allowed email addresses (e.g., "user1@example.com,user2@example.com") - `ALLOWED_USERS`: Comma-separated list of allowed email addresses (e.g., `"user1@example.com,user2@example.com"`)
The web API provides the following endpoints: The web API provides the following endpoints:
@@ -103,7 +109,8 @@ The web API provides the following endpoints:
- `/callback` - Handle OIDC callback - `/callback` - Handle OIDC callback
- `/userinfo` - Get user information - `/userinfo` - Get user information
All endpoints (except `/health`, `/login`, and `/callback`) require an Authorization header with a Bearer token. All endpoints (except `/health`, `/login`, and `/callback`) require an
Authorization header with a Bearer token.
## mobile functions ## mobile functions
@@ -198,9 +205,16 @@ To use a specific configuration section:
```text ```text
❯ myice ai ❯ myice ai
> prochain match u13 top ? > prochain match u13 top ?
< Le prochain match de l'équipe U13 Top se déroulera le dimanche 10 novembre 2024 contre HC Ajoie à la Raffeisen Arena de Porrentruy. Le match débutera à 14h00 et se terminera à 16h15. < Le prochain match de l'équipe U13 Top se déroulera le dimanche 10 novembre
2024 contre HC Ajoie à la Raffeisen Arena de Porrentruy. Le match débutera
à 14h00 et se terminera à 16h15.
> et les u13 a ? > et les u13 a ?
< Le prochain match de l'équipe U13 A se déroulera le samedi 9 novembre 2024 contre HC Vallorbe à P. du Frézillon, 1337 Vallorbe VD. Le match débutera à 13h00 et se terminera à 15h00. Le prochain match à domicile de l'équipe U13 A se déroulera le dimanche 10 novembre 2024 contre CP Meyrin à Les Vernets, Glace extérieure, 1227 Les Acacias GE. Le match débutera à 13h00 et se terminera à 15h00. < Le prochain match de l'équipe U13 A se déroulera le samedi 9 novembre 2024
contre HC Vallorbe à P. du Frézillon, 1337 Vallorbe VD. Le match débutera à
13h00 et se terminera à 15h00. Le prochain match à domicile de l'équipe
U13 A se déroulera le dimanche 10 novembre 2024 contre CP Meyrin à
Les Vernets, Glace extérieure, 1227 Les Acacias GE. Le match débutera
à 13h00 et se terminera à 15h00.
``` ```
To use a specific configuration section: To use a specific configuration section:
Executable
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
set -euo pipefail
# Build CSS using Tailwind CSS standalone CLI
# For local development, run:
# ./build-css.sh # one-off build
# ./build-css.sh --watch # watch mode
version="v3.4.17"
os="$(uname -s)"
arch="$(uname -m)"
case "$os" in
Linux*) platform="linux";;
Darwin*) platform="macos";;
*) echo "Unsupported OS: $os"; exit 1;;
esac
case "$arch" in
x86_64) arch="x64";;
arm64) arch="arm64";;
aarch64) arch="arm64";;
*) echo "Unsupported architecture: $arch"; exit 1;;
esac
binary="tailwindcss-${platform}-${arch}"
url="https://github.com/tailwindlabs/tailwindcss/releases/download/${version}/${binary}"
if [ ! -f "tailwindcss" ]; then
echo "Downloading Tailwind CSS standalone CLI (${version} ${platform}/${arch})..."
curl -sL "$url" -o tailwindcss
chmod +x tailwindcss
fi
if [ "${1:-}" == "--watch" ]; then
echo "Building CSS in watch mode..."
./tailwindcss -i ./style-input.css -o ./style.css --watch
else
echo "Building CSS..."
./tailwindcss -i ./style-input.css -o ./style.css --minify
fi
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
set -e
REGISTRY="harbor.cl1.parano.ch/library"
IMAGE="myice"
current_commit=$(git rev-parse HEAD)
git_tag=$(git tag --points-at "$current_commit")
if [[ -z $git_tag ]]; then
echo "Build only works on a git tag" >&2
exit 1
fi
docker build --platform linux/amd64 -t "$REGISTRY/$IMAGE:$git_tag" .
docker push "$REGISTRY/$IMAGE:$git_tag"
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
exec /var/www/.local/bin/poetry run fastapi run myice/webapi.py
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,350 @@
<!-- markdownlint-disable-file -->
# MyIce Flutter App - Design Specification
**Date:** 2026-07-06
**Context:** Application mobile Flutter (iOS/Android) reproduisant le fonctionnement de la webapp myice existante.
**Backend:** L'application se connecte a l'API FastAPI existante (myice/webapi.py) via HTTP.
## 1. Architecture globale
Approche : **Repository + Provider (B)**
```
lib/
├── main.dart # Entry point, MaterialApp, MultiProvider
├── config/
│ └── api_config.dart # URL base de l'API, timeouts
├── models/
│ ├── account.dart # {name, label}
│ ├── event.dart # Game/Practice du schedule
│ ├── event_detail.dart # Details + convocation
│ ├── user_info.dart # {email}
│ └── convocation.dart # Player, Staff, Convocation
├── services/
│ ├── api_service.dart # Dio + intercepteur Bearer
│ └── auth_service.dart # Login/logout OIDC, deep link
├── providers/
│ ├── auth_provider.dart # Etat auth + user info
│ ├── schedule_provider.dart # Liste events + filtres
│ └── event_provider.dart # Details d'un event (optionnel)
├── repositories/
│ └── myice_repository.dart # Appels API : accounts, schedule, game
├── screens/
│ ├── login_screen.dart
│ ├── schedule_screen.dart
│ └── event_detail_screen.dart
└── widgets/
├── event_card.dart
├── filter_dropdown.dart
└── loading_indicator.dart
```
**Pattern :** Repository encapsule les appels API -> Providers gerent l'etat reactif -> Screens ecoutent les Providers.
## 2. Dependances Flutter
```yaml
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0
provider: ^6.1.0
shared_preferences: ^2.2.0
flutter_web_auth: ^0.6.0
intl: ^0.19.0 # Pour le parsing/formatage de dates si besoin
```
## 3. Modeles de donnees (JSON <-> Dart)
### Account
```dart
class Account {
final String name;
final String label;
Account({required this.name, required this.label});
factory Account.fromJson(Map<String, dynamic> json) => Account(
name: json['name'] as String,
label: json['label'] as String,
);
}
```
### Event
Provenant de `/schedule`.
```dart
class Event {
final String idEvent;
final String agegroup;
final String name;
final String title;
final String opponent;
final String place;
final String start;
final String end;
final String? color;
final String eventType; // mappe depuis le champ JSON 'event'
Event({
required this.idEvent,
required this.agegroup,
required this.name,
required this.title,
required this.opponent,
required this.place,
required this.start,
required this.end,
this.color,
required this.eventType,
});
factory Event.fromJson(Map<String, dynamic> json) => Event(
idEvent: json['id_event']?.toString() ?? '',
agegroup: json['agegroup'] as String,
name: json['name'] as String? ?? '',
title: json['title'] as String,
opponent: json['opponent'] as String? ?? '',
place: json['place'] as String? ?? '',
start: json['start'] as String,
end: json['end'] as String,
color: json['color'] as String?,
eventType: json['event'] as String? ?? '',
);
}
```
### Player
```dart
class Player {
final String? position;
final String? number;
final String fname;
final String lname;
final String dob;
Player({this.position, this.number, required this.fname, required this.lname, required this.dob});
factory Player.fromJson(Map<String, dynamic> json) => Player(
position: json['position'] as String?,
number: json['number']?.toString(),
fname: json['fname'] as String,
lname: json['lname'] as String,
dob: json['dob'] as String,
);
}
```
### Staff
```dart
class Staff {
final String role;
final String fname;
final String lname;
Staff({required this.role, required this.fname, required this.lname});
factory Staff.fromJson(Map<String, dynamic> json) => Staff(
role: json['role'] as String,
fname: json['fname'] as String,
lname: json['lname'] as String,
);
}
```
### Convocation
```dart
class Convocation {
final List<Player> available;
final List<Staff> staff;
Convocation({required this.available, required this.staff});
factory Convocation.fromJson(Map<String, dynamic> json) => Convocation(
available: (json['available'] as List? ?? [])
.map((e) => Player.fromJson(e as Map<String, dynamic>))
.toList(),
staff: (json['staff'] as List? ?? [])
.map((e) => Staff.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
```
### EventDetail
Provenant de `/game/{id}`.
```dart
class EventDetail {
final String title;
final String type;
final String place;
final String timeStart;
final String timeEnd;
final Convocation convocation;
EventDetail({
required this.title,
required this.type,
required this.place,
required this.timeStart,
required this.timeEnd,
required this.convocation,
});
factory EventDetail.fromJson(Map<String, dynamic> json) => EventDetail(
title: json['title'] as String,
type: json['type'] as String,
place: json['place'] as String? ?? '',
timeStart: json['time_start'] as String,
timeEnd: json['time_end'] as String,
convocation: Convocation.fromJson(json['convocation'] as Map<String, dynamic>),
);
}
```
### UserInfo
```dart
class UserInfo {
final String email;
UserInfo({required this.email});
factory UserInfo.fromJson(Map<String, dynamic> json) => UserInfo(
email: json['email'] as String? ?? '',
);
}
```
## 4. Services
### ApiService
Utilise `dio`. Un intercepteur ajoute automatiquement le header `Authorization: Bearer <token>`.
Concept:
- `setBearerToken(token)` ajoute un `InterceptorsWrapper` a Dio.
- `clearBearerToken()` retire les intercepteurs existants.
- Methodes: `getAccounts()`, `getSchedule(account)`, `getGameDetail(gameId, account)`, `getUserInfo()`.
- Retournent des objets modeles via `fromJson`.
### AuthService
Utilise `flutter_web_auth` pour l'OIDC + `shared_preferences` pour stocker le token.
Concept:
- `storedToken`, `storedAccount` depuis `SharedPreferences`.
- `login()`: appelle `FlutterWebAuth.authenticate(url: baseUrl/login, callbackUrlScheme: 'myice')`. Parse le fragment `#access_token=xxx` de l'URL retournee. Stocke le token.
- `setAccount(account)`: persiste le compte selectionne.
- `logout()`: supprime token, account, userInfo des prefs.
**Configuration deep link requise:**
- Android: intent-filter dans `AndroidManifest.xml` pour scheme `myice://`.
- iOS: `CFBundleURLTypes` dans `Info.plist` avec scheme `myice`.
## 5. Providers (Provider package)
### AuthProvider
Gere l'etat global d'authentification.
**Etat:** `isAuthenticated`, `userInfo`.
**Methodes:**
- `init()`: Verifie token stocke, appelle `/userinfo`. Si 401, considere deconnecte.
- `login()`: Lance AuthService.login(), met a jour le token, appelle `/userinfo`, notifie listeners.
- `logout()`: Efface le token, notifie listeners.
### ScheduleProvider
Gere les events, les comptes, et les filtres.
**Etat:** `accounts`, `events`, `selectedAccount`, `selectedAgegroup`, `selectedSubgroup`, `isLoading`, `error`.
**Methodes:**
- `loadAccounts()`: Appelle `/accounts`, met a jour `accounts`. Selectionne `storedAccount` si present, sinon le premier.
- `loadSchedule()`: Met `isLoading = true`, appelle `/schedule?account=selectedAccount`, filtre events ou `eventType == "Jeu"`, extrait les `agegroup` uniques pour les filtres.
- `setAccount(String)`: Met a jour `selectedAccount`, sauvegarde dans SharedPreferences, appelle `loadSchedule()`.
- `setAgegroup(String)`: Met a jour, reset `selectedSubgroup`, notifie.
- `setSubgroup(String)`: Met a jour, notifie.
- `get filteredEvents`: Getter filtre `events` par `agegroup` et `subgroup`.
**Logique de filtre sous-groupe:**
Identique a la webapp. La fonction `extractSubgroup(name)` retire la partie apres `" - "` dans le `name` de l'event pour obtenir le sous-groupe (si applicable).
### EventDetailProvider (Optionnel)
Gere le chargement des details d'un event. Peut etre integre dans `ScheduleProvider` si prefere pour garder un seul provider.
**Etat:** `eventDetail`, `isLoading`, `error`.
**Methodes:** `loadEventDetail(gameId)` appelle `/game/{gameId}`.
## 6. Ecrans
### LoginScreen
- **AppBar:** Aucune (centre).
- **Body:** Column centree.
- Titre: "MyIce - Games" (headlineLarge, couleur indigo).
- Bouton: "Se connecter avec Infomaniak" (style degrade indigo -> violet).
- Texte d'aide: "Connectez-vous pour acceder aux evenements...".
- **Comportement:**
- Si `authProvider.isAuthenticated`, navigue automatiquement vers `ScheduleScreen`.
- Affiche `CircularProgressIndicator` pendant le login.
- SnackBar en cas d'erreur.
### ScheduleScreen
- **AppBar:** Titre "MyIce", action logout + email utilisateur.
- **Body:**
- **Filtres:** Card avec 3 `DropdownButtonFormField` (Compte, Age, Sous-groupe). Le bouton "Charger" est optionnel (changement de compte recharge auto).
- **Liste:** `Expanded` + `ListView.builder`. Affiche `EventCard` pour chaque event filtre.
- Etats: Loading (`CircularProgressIndicator`), vide (texte info), erreur (SnackBar).
### EventDetailScreen
- **AppBar:** Titre = `event.title`, bouton retour.
- **Body:** `SingleChildScrollView` + `Padding(16)`.
- Card recap: Type, Lieu, Heure.
- Section Joueurs:
- Si vide: Card amber "Aucun joueur convoque".
- Sinon: Card avec total + repartition positions. Liste triee par position puis numero: `[Position] #Num - Prenom Nom (DOB)`.
- Section Personnel:
- Si vide: Card bleu "Aucun personnel convoque".
- Sinon: Liste `role: Prenom Nom`.
- Si aucun joueur ET aucun personnel: Card amber principal "Aucun joueur ni personnel convoque" uniquement.
### Widgets reutilisables
- **EventCard:** Card avec bordure laterale coloree. Titre (`agegroup - name`), sous-titre, details (adversaire, lieu, heure). onTap -> `EventDetailScreen`.
- **FilterDropdown:** `DropdownButtonFormField` generique stylise.
## 7. Navigation & Routing
Utilisation de `Navigator` classique (pas de package tierce pour 3 ecrans).
- `main.dart`: `MaterialApp` avec `home` determine par `Consumer<AuthProvider>` (`ScheduleScreen` ou `LoginScreen`).
- Navigation details: `Navigator.push(MaterialPageRoute(builder: (_) => EventDetailScreen(...)))`.
## 8. Gestion des erreurs
- Erreur reseau / API: SnackBar rouge avec message.
- 401 (Token expire): Deconnexion automatique + redirection `LoginScreen`.
- JSON malforme: La webapp gere cote backend. Cote Flutter, `jsonDecode` echoue -> message "Donnees invalides".
- Aucun event/joueur: Interface vide avec message informatif (pas d'erreur).
## 9. Configuration requise
### Android
Ajouter un intent-filter dans `android/app/src/main/AndroidManifest.xml` pour le deep link `myice://callback`.
### iOS
Ajouter `CFBundleURLTypes` dans `ios/Runner/Info.plist` avec scheme `myice`.
### Configuration API
`lib/config/api_config.dart`:
```dart
class ApiConfig {
static const String baseUrl = 'https://your-api-domain.com'; // A configurer
}
```
## 10. Plan d'implementation (High-level)
1. Scaffolding du projet Flutter (`flutter create`).
2. Ajout des dependances (`pubspec.yaml`).
3. Configuration des deep links (`AndroidManifest` + `Info.plist`).
4. Implementation des modeles de donnees (`fromJson`).
5. Implementation des services (`ApiService`, `AuthService`).
6. Implementation des providers (`AuthProvider`, `ScheduleProvider`).
7. Implementation des ecrans (`LoginScreen`, `ScheduleScreen`, `EventDetailScreen`).
8. Implementation des widgets (`EventCard`, etc.).
9. Wiring dans `main.dart` avec `MultiProvider`.
10. Tests manuels de l'auth OIDC et de la navigation.
---
**Prochaine etape apres validation du spec:** Creer le plan d'implementation detaille avec le skill `writing-plans`.
+334 -126
View File
@@ -5,70 +5,95 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MyIce - Games</title> <title>MyIce - Games</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="/style.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <link rel="icon" href="favicon.ico" type="image/x-icon">
</head> </head>
<body> <body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
<div id="connectedUser" class="position-fixed top-0 end-0 m-2 p-2 bg-light border rounded" <!-- Connected User Panel -->
style="display: none; z-index: 1000; font-size: 0.9rem;"> <div id="connectedUser" class="fixed top-4 right-4 bg-white bg-opacity-80 backdrop-blur-sm rounded-xl shadow-lg p-4 flex items-center hidden z-50">
<span>Connecté: <span id="userName"></span></span> <span class="text-indigo-700 font-medium">Connecté: <span id="userName" class="font-semibold"></span></span>
<button id="disconnect" class="btn btn-danger btn-sm ms-2" <button id="disconnect" class="ml-3 bg-rose-500 hover:bg-rose-600 text-white text-sm px-3 py-1 rounded-lg transition duration-200">Déco</button>
style="font-size: 0.8rem; padding: 0.1rem 0.3rem;">Déco</button>
</div> </div>
<div class="container-fluid d-flex align-items-center justify-content-center"> <!-- Login View -->
<div class="min-h-screen flex items-center justify-center p-4">
<div class="text-center"> <div class="text-center">
<h1 id="loginTitle" class="mb-4">MyIce - Games</h1> <h1 id="loginTitle" class="text-4xl md:text-5xl font-bold text-indigo-800 mb-8">MyIce - Games</h1>
<div id="loginContainer" class="mb-3"> <div id="loginContainer" class="mb-6">
<div id="oidcLoginSection"> <div id="oidcLoginSection">
<button id="oidcLoginBtn" class="btn btn-primary btn-md px-4 py-3 shadow">Se connecter</button> <button id="oidcLoginBtn" class="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold text-lg px-8 py-4 rounded-2xl shadow-xl transform hover:scale-105 transition-all duration-300">
Se connecter
</button>
</div> </div>
</div> </div>
<div class="mt-8 max-w-md mx-auto">
<p class="text-gray-600 text-sm">
Connectez-vous pour accéder aux événements et informations de votre équipe
</p>
</div>
</div> </div>
</div> </div>
<div class="container mt-2" id="mainContent" style="display: none;"> <!-- Main Content -->
<div id="mainContent" class="container mx-auto px-4 py-6 hidden">
<div id="eventFilters" style="display: none;"> <!-- Filters Section -->
<div class="mb-3"> <div id="eventFilters" class="bg-white rounded-2xl shadow-lg p-6 mb-8 hidden">
<label for="account" class="form-label">Compte</label> <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<select id="account" class="form-select"> <div>
<option value="default">Défaut</option> <label for="account" class="block text-sm font-medium text-gray-700 mb-1">Compte</label>
</select> <select id="account" class="w-full rounded-lg border border-gray-300 bg-white py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-300">
<label for="agegroup" class="form-label">Âge</label> <option value="default">Défaut</option>
<select id="agegroup" class="form-select"> </select>
<option value="">Tous</option>
</select>
<label for="subgroup" class="form-label">Sous-groupe</label>
<select id="subgroup" class="form-select">
<option value="">Tous</option>
</select>
<button id="fetchEvents" class="btn btn-primary" style="margin-top: 1.2rem;">Charger</button>
</div>
</div>
<div id="loadingIndicator" class="text-center my-3" style="display: none;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Chargement...</span>
</div>
<p class="mt-2">Chargement des événements...</p>
</div>
<div id="eventList" class="row mt-2"></div>
<!-- Modal pour afficher les détails d'un événement -->
<div class="modal fade" id="eventDetailsModal" tabindex="-1" aria-labelledby="eventDetailsLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="eventDetailsLabel">Détails de l'événement</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="eventDetailsContent">Chargement...</div>
</div> </div>
<div>
<label for="agegroup" class="block text-sm font-medium text-gray-700 mb-1">Âge</label>
<select id="agegroup" class="w-full rounded-lg border border-gray-300 bg-white py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-300">
<option value="">Tous</option>
</select>
</div>
<div>
<label for="subgroup" class="block text-sm font-medium text-gray-700 mb-1">Sous-groupe</label>
<select id="subgroup" class="w-full rounded-lg border border-gray-300 bg-white py-2 px-3 text-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-300">
<option value="">Tous</option>
</select>
</div>
<div class="flex items-end">
<button id="fetchEvents" class="w-full bg-indigo-500 hover:bg-indigo-600 text-white font-medium py-2 px-4 rounded-lg transition duration-200">
Charger
</button>
</div>
</div>
</div>
<!-- Loading Indicator -->
<div id="loadingIndicator" class="text-center py-12 hidden">
<div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500 mb-4"></div>
<p class="text-indigo-700 font-medium">Chargement des événements...</p>
</div>
<!-- Events List -->
<div id="eventList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"></div>
</div>
<!-- Event Details Modal -->
<div id="eventDetailsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<div class="border-b border-gray-200 p-6 flex justify-between items-center">
<h5 id="eventDetailsLabel" class="text-2xl font-bold text-gray-800">Détails de l'événement</h5>
<button id="closeModal" class="text-gray-500 hover:text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div id="eventDetailsContent" class="p-6">
Chargement...
</div> </div>
</div> </div>
</div> </div>
@@ -85,6 +110,8 @@
const eventList = document.getElementById("eventList"); const eventList = document.getElementById("eventList");
const fetchButton = document.getElementById("fetchEvents"); const fetchButton = document.getElementById("fetchEvents");
const eventDetailsContent = document.getElementById("eventDetailsContent"); const eventDetailsContent = document.getElementById("eventDetailsContent");
const eventDetailsModal = document.getElementById("eventDetailsModal");
const closeModal = document.getElementById("closeModal");
const apiBaseUrl = window.location.origin; const apiBaseUrl = window.location.origin;
let storedApiKey = localStorage.getItem("apikey"); let storedApiKey = localStorage.getItem("apikey");
@@ -92,6 +119,25 @@
let storedAccount = localStorage.getItem("account") || "default"; let storedAccount = localStorage.getItem("account") || "default";
let userInfo = JSON.parse(localStorage.getItem("userInfo") || "null"); let userInfo = JSON.parse(localStorage.getItem("userInfo") || "null");
// Close modal when close button is clicked
closeModal.addEventListener("click", () => {
eventDetailsModal.classList.add("hidden");
});
// Close modal when clicking outside the content
eventDetailsModal.addEventListener("click", (e) => {
if (e.target === eventDetailsModal) {
eventDetailsModal.classList.add("hidden");
}
});
// Close modal when pressing ESC key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !eventDetailsModal.classList.contains("hidden")) {
eventDetailsModal.classList.add("hidden");
}
});
// If we have an API key but no userInfo, fetch it from the server // If we have an API key but no userInfo, fetch it from the server
if (storedApiKey && !userInfo) { if (storedApiKey && !userInfo) {
fetch(`${apiBaseUrl}/userinfo`, { fetch(`${apiBaseUrl}/userinfo`, {
@@ -194,18 +240,18 @@
function renderLoginSection() { function renderLoginSection() {
const mainContent = document.getElementById("mainContent"); const mainContent = document.getElementById("mainContent");
const loginView = document.querySelector(".container-fluid"); const loginView = document.querySelector(".min-h-screen.flex");
const connectedUser = document.getElementById("connectedUser"); const connectedUser = document.getElementById("connectedUser");
const loginTitle = document.getElementById("loginTitle"); const loginTitle = document.getElementById("loginTitle");
if (storedApiKey || userInfo) { if (storedApiKey || userInfo) {
// User is logged in // User is logged in
loginView.style.display = "none"; loginView.classList.add("hidden");
mainContent.style.display = "block"; mainContent.classList.remove("hidden");
loginTitle.style.display = "none"; loginTitle.classList.add("hidden");
connectedUser.style.display = "block"; connectedUser.classList.remove("hidden");
oidcLoginSection.style.display = "none"; oidcLoginSection.classList.add("hidden");
const userNameElement = document.getElementById("userName"); const userNameElement = document.getElementById("userName");
if (userInfo && userInfo.email) { if (userInfo && userInfo.email) {
@@ -214,17 +260,17 @@
userNameElement.textContent = "Utilisateur"; userNameElement.textContent = "Utilisateur";
} }
eventFilters.style.display = "block"; eventFilters.classList.remove("hidden");
updateAccountOptionsAndLoadEvents(); updateAccountOptionsAndLoadEvents();
} else { } else {
// User is not logged in // User is not logged in
loginView.style.display = "flex"; loginView.classList.remove("hidden");
mainContent.style.display = "none"; mainContent.classList.add("hidden");
loginTitle.style.display = "block"; loginTitle.classList.remove("hidden");
connectedUser.style.display = "none"; connectedUser.classList.add("hidden");
oidcLoginSection.style.display = "block"; oidcLoginSection.classList.remove("hidden");
eventFilters.style.display = "none"; eventFilters.classList.add("hidden");
// Remove any existing event listeners to prevent duplicates // Remove any existing event listeners to prevent duplicates
const oidcLoginBtn = document.getElementById("oidcLoginBtn"); const oidcLoginBtn = document.getElementById("oidcLoginBtn");
@@ -353,7 +399,7 @@
// Show loading indicator // Show loading indicator
const loadingIndicator = document.getElementById("loadingIndicator"); const loadingIndicator = document.getElementById("loadingIndicator");
const eventList = document.getElementById("eventList"); const eventList = document.getElementById("eventList");
loadingIndicator.style.display = "block"; loadingIndicator.classList.remove("hidden");
eventList.innerHTML = ""; // Clear previous events eventList.innerHTML = ""; // Clear previous events
fetch(`${apiBaseUrl}/schedule?account=${account}`, { fetch(`${apiBaseUrl}/schedule?account=${account}`, {
@@ -392,7 +438,7 @@
}) })
.then(data => { .then(data => {
// Hide loading indicator // Hide loading indicator
loadingIndicator.style.display = "none"; loadingIndicator.classList.add("hidden");
if (data) { if (data) {
// Check if data is an array // Check if data is an array
@@ -408,7 +454,7 @@
}) })
.catch(error => { .catch(error => {
// Hide loading indicator on error // Hide loading indicator on error
loadingIndicator.style.display = "none"; loadingIndicator.classList.add("hidden");
console.error("Erreur lors du chargement des événements:", error); console.error("Erreur lors du chargement des événements:", error);
alert("Erreur lors du chargement des événements: " + error.message); alert("Erreur lors du chargement des événements: " + error.message);
@@ -429,6 +475,17 @@
subgroupSelect.innerHTML = '<option value="">Tous</option>'; subgroupSelect.innerHTML = '<option value="">Tous</option>';
} }
function extractSubgroup(name) {
// Extract subgroup from name by removing opponent part after " - "
if (!name) return "";
const parts = name.split(" - ");
if (parts.length > 1) {
parts.pop(); // Remove opponent
return parts.join(" - ");
}
return name;
}
function updateSubgroupOptions(selectedAgegroup, events) { function updateSubgroupOptions(selectedAgegroup, events) {
// Reset subgroup options // Reset subgroup options
subgroupSelect.innerHTML = '<option value="">Tous</option>'; subgroupSelect.innerHTML = '<option value="">Tous</option>';
@@ -447,10 +504,9 @@
events events
.filter(event => event.agegroup === selectedAgegroup) .filter(event => event.agegroup === selectedAgegroup)
.forEach(event => { .forEach(event => {
// Extract subgroup from event.name or event.title // Extract subgroup from event.name by removing opponent
// This assumes the subgroup is part of the name field
if (event.name && event.name !== selectedAgegroup) { if (event.name && event.name !== selectedAgegroup) {
subgroups.add(event.name); subgroups.add(extractSubgroup(event.name));
} }
}); });
@@ -475,30 +531,63 @@
if (selectedAgegroup !== "" && event.agegroup !== selectedAgegroup) return false; if (selectedAgegroup !== "" && event.agegroup !== selectedAgegroup) return false;
// Filter by subgroup // Filter by subgroup
if (selectedSubgroup !== "" && event.name !== selectedSubgroup) return false; let eventSubgroup = extractSubgroup(event.name);
if (selectedSubgroup !== "" && eventSubgroup !== selectedSubgroup) return false;
return true; return true;
}); });
if (filteredEvents.length === 0) { if (filteredEvents.length === 0) {
eventList.innerHTML = "<p class='text-muted'>Aucun événement 'Jeu' trouvé.</p>"; eventList.innerHTML = "<p class='text-gray-500 text-center py-8'>Aucun événement 'Jeu' trouvé.</p>";
return; return;
} }
filteredEvents.forEach(event => { filteredEvents.forEach(event => {
const eventCard = document.createElement("div"); const eventCard = document.createElement("div");
eventCard.className = "col-md-4 mb-3"; eventCard.className = "bg-white rounded-2xl shadow-lg overflow-hidden transform hover:scale-105 transition-all duration-300 cursor-pointer";
eventCard.innerHTML = `
<div class="card" style="border-left: 5px solid ${event.color}" data-id="${event.id_event}"> const card = document.createElement("div");
<div class="card-body"> card.className = "p-6 border-l-4";
<h5 class="card-title">${event.agegroup} - ${event.name}</h5> card.style.borderColor = event.color || "#818cf8";
<p class="card-text">${event.title}</p>
<p class="card-text"><strong>Adversaire:</strong> ${event.opponent}</p> const inner = document.createElement("div");
<p class="card-text"><strong>Lieu:</strong> ${event.place}</p> inner.className = "flex justify-between items-start";
<p class="card-text"><strong>Heure:</strong> ${event.start} - ${event.end}</p>
</div> const innerDiv = document.createElement("div");
</div> const h3 = document.createElement("h3");
`; h3.className = "text-xl font-bold text-gray-800";
h3.textContent = event.agegroup + " - " + event.name;
const subtitle = document.createElement("p");
subtitle.className = "text-gray-600 mt-1";
subtitle.textContent = event.title;
innerDiv.appendChild(h3);
innerDiv.appendChild(subtitle);
inner.appendChild(innerDiv);
card.appendChild(inner);
const detailsDiv = document.createElement("div");
detailsDiv.className = "mt-4 space-y-2";
const p1 = document.createElement("p");
p1.className = "flex items-center text-gray-700";
p1.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg><strong>Adversaire:</strong> ';
p1.appendChild(document.createTextNode(event.opponent));
detailsDiv.appendChild(p1);
const p2 = document.createElement("p");
p2.className = "flex items-center text-gray-700";
p2.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" /></svg><strong>Lieu:</strong> ';
p2.appendChild(document.createTextNode(event.place));
detailsDiv.appendChild(p2);
const p3 = document.createElement("p");
p3.className = "flex items-center text-gray-700";
p3.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg><strong>Heure:</strong> ';
p3.appendChild(document.createTextNode(event.start + " - " + event.end));
detailsDiv.appendChild(p3);
card.appendChild(detailsDiv);
eventCard.appendChild(card);
eventCard.addEventListener("click", () => fetchEventDetails(event.id_event)); eventCard.addEventListener("click", () => fetchEventDetails(event.id_event));
eventList.appendChild(eventCard); eventList.appendChild(eventCard);
}); });
@@ -510,54 +599,173 @@
headers: { "Authorization": `Bearer ${storedApiKey}` } headers: { "Authorization": `Bearer ${storedApiKey}` }
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
const sortedPlayers = data.convocation.available const availablePlayers = data.convocation.available || [];
.sort((a, b) => (a.number || 0) - (b.number || 0)); const sortedPlayers = availablePlayers
.sort((a, b) => (a.number || 0) - (b.number || 0));
// Calculate player statistics const totalPlayers = sortedPlayers.length;
const totalPlayers = sortedPlayers.length; const positionCount = {};
const positionCount = {}; sortedPlayers.forEach(player => {
sortedPlayers.forEach(player => { const position = player.position || "N/A";
const position = player.position || "N/A"; positionCount[position] = (positionCount[position] || 0) + 1;
positionCount[position] = (positionCount[position] || 0) + 1; });
});
// Generate position breakdown const positionBreakdown = Object.entries(positionCount)
const positionBreakdown = Object.entries(positionCount) .map(([position, count]) => position + ": " + count)
.map(([position, count]) => `${position}: ${count}`) .join(", ");
.join(', ');
// Sort players by position first, then by number const playersByPosition = [...sortedPlayers].sort((a, b) => {
const playersByPosition = [...sortedPlayers].sort((a, b) => { const positionA = a.position || "ZZZ";
// Sort by position first const positionB = b.position || "ZZZ";
const positionA = a.position || "ZZZ"; // Put undefined positions at the end if (positionA !== positionB) {
const positionB = b.position || "ZZZ"; return positionA.localeCompare(positionB);
}
const numA = parseInt(a.number) || 0;
const numB = parseInt(b.number) || 0;
return numA - numB;
});
if (positionA !== positionB) { const staffList = data.convocation.staff || [];
return positionA.localeCompare(positionB); const totalStaff = staffList.length;
}
// If positions are the same, sort by number eventDetailsContent.innerHTML = "";
const numA = parseInt(a.number) || 0;
const numB = parseInt(b.number) || 0;
return numA - numB;
});
eventDetailsContent.innerHTML = ` if (totalPlayers === 0 && totalStaff === 0) {
<h5>${data.title}</h5> const wrapper = document.createElement("div");
<p><strong>Type:</strong> ${data.type}</p> wrapper.className = "rounded-xl border border-amber-300 bg-amber-50 p-6";
<p><strong>Lieu:</strong> ${data.place}</p> const centerDiv = document.createElement("div");
<p><strong>Heure:</strong> ${data.time_start} - ${data.time_end}</p> centerDiv.className = "text-center";
<p><strong>Joueurs convoqués:</strong> ${totalPlayers} joueur${totalPlayers > 1 ? 's' : ''} (${positionBreakdown})</p> const h5 = document.createElement("h5");
<h6>Liste des joueurs:</h6> h5.className = "text-2xl font-bold text-gray-800 mb-4";
<ul>${playersByPosition.map(player => { h5.textContent = data.title;
let number = player.number ? player.number : "N/A"; centerDiv.appendChild(h5);
let position = player.position ? player.position : "N/A";
return `<li>[${position}] ${number} - ${player.fname} ${player.lname} (${player.dob})</li>`; const infoDiv = document.createElement("div");
}).join('')}</ul> infoDiv.className = "space-y-2 text-gray-700 mb-6";
`; const pType = document.createElement("p");
new bootstrap.Modal(document.getElementById('eventDetailsModal')).show(); pType.innerHTML = "<strong>Type:</strong> ";
}) pType.appendChild(document.createTextNode(data.type));
infoDiv.appendChild(pType);
const pLieu = document.createElement("p");
pLieu.innerHTML = "<strong>Lieu:</strong> ";
pLieu.appendChild(document.createTextNode(data.place));
infoDiv.appendChild(pLieu);
const pHeure = document.createElement("p");
pHeure.innerHTML = "<strong>Heure:</strong> ";
pHeure.appendChild(document.createTextNode(data.time_start + " - " + data.time_end));
infoDiv.appendChild(pHeure);
centerDiv.appendChild(infoDiv);
const msgDiv = document.createElement("div");
msgDiv.className = "bg-amber-100 border border-amber-300 text-amber-800 px-4 py-3 rounded-lg";
const msgH6 = document.createElement("h6");
msgH6.className = "font-bold text-lg mb-1";
msgH6.textContent = "Aucun joueur ni personnel convoqué";
msgDiv.appendChild(msgH6);
const msgP = document.createElement("p");
msgP.textContent = "Il n'y a actuellement aucun joueur ni personnel convoqué pour ce match.";
msgDiv.appendChild(msgP);
centerDiv.appendChild(msgDiv);
wrapper.appendChild(centerDiv);
eventDetailsContent.appendChild(wrapper);
} else {
const container = document.createElement("div");
const h5 = document.createElement("h5");
h5.className = "text-2xl font-bold text-gray-800 mb-4";
h5.textContent = data.title;
container.appendChild(h5);
const infoDiv = document.createElement("div");
infoDiv.className = "space-y-2 text-gray-700 mb-6";
const pType = document.createElement("p");
pType.innerHTML = "<strong>Type:</strong> ";
pType.appendChild(document.createTextNode(data.type));
infoDiv.appendChild(pType);
const pLieu = document.createElement("p");
pLieu.innerHTML = "<strong>Lieu:</strong> ";
pLieu.appendChild(document.createTextNode(data.place));
infoDiv.appendChild(pLieu);
const pHeure = document.createElement("p");
pHeure.innerHTML = "<strong>Heure:</strong> ";
pHeure.appendChild(document.createTextNode(data.time_start + " - " + data.time_end));
infoDiv.appendChild(pHeure);
container.appendChild(infoDiv);
if (totalPlayers === 0) {
const noPlayersDiv = document.createElement("div");
noPlayersDiv.className = "bg-amber-100 border border-amber-300 text-amber-800 px-4 py-3 rounded-lg";
const noPlayersH6 = document.createElement("h6");
noPlayersH6.className = "font-bold mb-1";
noPlayersH6.textContent = "Aucun joueur convoqué";
noPlayersDiv.appendChild(noPlayersH6);
const noPlayersP = document.createElement("p");
noPlayersP.textContent = "Il n'y a actuellement aucun joueur convoqué pour ce match.";
noPlayersDiv.appendChild(noPlayersP);
container.appendChild(noPlayersDiv);
} else {
const playersP = document.createElement("p");
playersP.innerHTML = "<strong>Joueurs convoqués:</strong> ";
playersP.appendChild(document.createTextNode(totalPlayers + " joueur" + (totalPlayers > 1 ? "s" : "") + " (" + positionBreakdown + ")"));
infoDiv.appendChild(playersP);
const playersDiv = document.createElement("div");
const playersH6 = document.createElement("h6");
playersH6.className = "text-xl font-semibold text-gray-800 mb-3";
playersH6.textContent = "Liste des joueurs:";
playersDiv.appendChild(playersH6);
const playersUl = document.createElement("ul");
playersUl.className = "grid grid-cols-1 md:grid-cols-2 gap-3";
playersByPosition.forEach(player => {
const li = document.createElement("li");
li.className = "bg-indigo-50 rounded-lg p-3";
const number = player.number ? player.number : "N/A";
const position = player.position ? player.position : "N/A";
li.textContent = "[" + position + "] " + number + " - " + player.fname + " " + player.lname + " (" + player.dob + ")";
playersUl.appendChild(li);
});
playersDiv.appendChild(playersUl);
container.appendChild(playersDiv);
}
if (totalStaff > 0) {
const staffDiv = document.createElement("div");
staffDiv.className = "mt-6";
const staffH6 = document.createElement("h6");
staffH6.className = "text-xl font-semibold text-gray-800 mb-3";
staffH6.textContent = "Personnel (" + totalStaff + "):";
staffDiv.appendChild(staffH6);
const staffUl = document.createElement("ul");
staffUl.className = "space-y-2";
staffList.forEach(staff => {
const li = document.createElement("li");
li.className = "flex items-center";
const roleSpan = document.createElement("span");
roleSpan.className = "font-medium w-32";
roleSpan.textContent = staff.role + ":";
li.appendChild(roleSpan);
li.appendChild(document.createTextNode(" " + staff.fname + " " + staff.lname));
staffUl.appendChild(li);
});
staffDiv.appendChild(staffUl);
container.appendChild(staffDiv);
} else {
const noStaffDiv = document.createElement("div");
noStaffDiv.className = "mt-6 bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg";
const noStaffH6 = document.createElement("h6");
noStaffH6.className = "font-bold mb-1";
noStaffH6.textContent = "Aucun personnel convoqué";
noStaffDiv.appendChild(noStaffH6);
const noStaffP = document.createElement("p");
noStaffP.textContent = "Il n'y a actuellement aucun personnel convoqué pour ce match.";
noStaffDiv.appendChild(noStaffP);
container.appendChild(noStaffDiv);
}
eventDetailsContent.appendChild(container);
}
eventDetailsModal.classList.remove("hidden");
})
.catch(error => console.error("Erreur lors du chargement des détails de l'événement:", error)); .catch(error => console.error("Erreur lors du chargement des détails de l'événement:", error));
} }
}); });
+51 -14
View File
@@ -14,7 +14,7 @@ from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Annotated from typing import Annotated
from typing import List, Tuple from typing import List, Tuple
import PyPDF2 import pypdf
import requests import requests
import typer import typer
from rich import print from rich import print
@@ -140,14 +140,17 @@ class AgeGroup(str, Enum):
u13a = "U13 (A)" u13a = "U13 (A)"
u13p = "U13 (Prép)" u13p = "U13 (Prép)"
u14ev = "U14 (Elite Vernets)" u14ev = "U14 (Elite Vernets)"
u14sae = "U14 (Groupe SAE)"
u14esmv = "U14 (Elite Sous-Moulin/Vergers)" u14esmv = "U14 (Elite Sous-Moulin/Vergers)"
u15t = "U15 (Top)" u15t = "U15 (Top)"
u15a = "U15 (A)" u15a = "U15 (A)"
u16e = "U16 (Elite)" u16e = "U16 (Elit)"
u16t = "U16 (Top GS Ass)" u16t = "U16 (Top GS Ass)"
u18e = "U18 (Elite)" u18e = "U18 (Elit)"
u18el = "U18 (Elit)" u21e = "U21 (Elit)"
u21e = "U21 (ELIT)" u14t = "U14 (Top)"
u14t1 = "U14 (Top1)"
u14t2 = "U14 (Top2)"
def normalize_age_group(value: str) -> AgeGroup | None: def normalize_age_group(value: str) -> AgeGroup | None:
@@ -331,7 +334,7 @@ def get_schedule(num_days: int) -> str:
global userid global userid
assert session and userid assert session and userid
now = datetime.datetime.now() now = datetime.datetime.now()
date_start = now date_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
date_end = date_start + datetime.timedelta(days=num_days) date_end = date_start + datetime.timedelta(days=num_days)
r = session.post( r = session.post(
"https://app.myice.hockey/inc/processclubplanning.php", "https://app.myice.hockey/inc/processclubplanning.php",
@@ -415,27 +418,31 @@ def schedule(
def os_open(file: str) -> None: def os_open(file: str) -> None:
import subprocess
if os.uname().sysname == "Linux": if os.uname().sysname == "Linux":
os.system(f"xdg-open {file}") subprocess.run(["xdg-open", file])
else: else:
print(f"Opening file {file}", file=sys.stderr) print(f"Opening file {file}", file=sys.stderr)
os.system(f"open {file}") subprocess.run(["open", file])
def extract_players(pdf_file: Path) -> List[str]: def extract_players(pdf_file: Path) -> List[str]:
reader = PyPDF2.PdfReader(pdf_file) reader = pypdf.PdfReader(pdf_file)
page = reader.pages[0] page = reader.pages[0]
players = [] players = []
def visitor_body(text, cm, tm, fontDict, fontSize): def visitor_body(text, cm, tm, fontDict, fontSize):
global last_text
if text:
last_text = text
(x, y) = (tm[4], tm[5]) (x, y) = (tm[4], tm[5])
# print(tm, text)
if x > 79 and x < 80 and y < 741.93: if x > 79 and x < 80 and y < 741.93:
# and y < 741.93 and y > 741.93 - 585.18: # and y < 741.93 and y > 741.93 - 585.18:
players.append(text) players.append(last_text.strip())
page.extract_text(visitor_text=visitor_body) page.extract_text(visitor_text=visitor_body, extraction_mode="plain")
return players return players
@@ -587,6 +594,18 @@ mobile_headers = {
} }
# def curl_for_mobile_login(username: str, password: str) -> str:
# """Generate curl command to replicate mobile_login request."""
# import base64
# headers_str = " ".join([f"-H '{k}: {v}'" for k, v in mobile_headers.items()])
# encoded_username = base64.b64encode(username.encode()).decode()
# encoded_password = base64.b64encode(password.encode()).decode()
# return f"""curl -X POST "https://app.myice.hockey/api/mobilerest/login" \
# {headers_str} \
# -d 'login_email={encoded_username}&login_password={encoded_password}&language=FR&v=2'"""
def mobile_login(config_section: str | None = None): def mobile_login(config_section: str | None = None):
global global_config_section global global_config_section
import base64 import base64
@@ -599,10 +618,13 @@ def mobile_login(config_section: str | None = None):
return {"id": 0, "token": token, "id_club": club_id} return {"id": 0, "token": token, "id_club": club_id}
print("Requesting token", file=sys.stderr) print("Requesting token", file=sys.stderr)
# Print curl equivalent for debugging
# print(curl_for_mobile_login(username, password), file=sys.stderr)
with requests.post( with requests.post(
"https://app.myice.hockey/api/mobilerest/login", "https://app.myice.hockey/api/mobilerest/login",
headers=mobile_headers, headers=mobile_headers,
data=f"login_email={base64.b64encode(username.encode()).decode()}&login_password={base64.b64encode(password.encode()).decode()}&language=FR&v=2", data=f"login_email={base64.b64encode(username.encode()).decode()}&login_password={base64.b64encode(password.encode()).decode()}&language=FR&v=2",
timeout=(5, 30),
# verify=False, # verify=False,
) as r: ) as r:
r.raise_for_status() r.raise_for_status()
@@ -621,11 +643,26 @@ userdata = {
} }
def refresh_data(): # def curl_for_refresh_data(token: str, club_id: int) -> str:
# """Generate curl command to replicate refresh_data request."""
# headers_str = " ".join([f"-H '{k}: {v}'" for k, v in mobile_headers.items()])
# return f"""curl -X POST "https://app.myice.hockey/api/mobilerest/refreshdata" \
# {headers_str} \
# -d 'token={token}&id_club={club_id}&language=FR'"""
def refresh_data(user_data=None):
# Use provided user_data or fall back to global userdata for backward compatibility
if user_data is None:
user_data = userdata
# Print curl equivalent for debugging
# print(
# curl_for_refresh_data(user_data["token"], user_data["id_club"]), file=sys.stderr
# )
with requests.post( with requests.post(
"https://app.myice.hockey/api/mobilerest/refreshdata", "https://app.myice.hockey/api/mobilerest/refreshdata",
headers=mobile_headers, headers=mobile_headers,
data=f"token={userdata['token']}&id_club={userdata['id_club']}&language=FR", data=f"token={user_data['token']}&id_club={user_data['id_club']}&language=FR",
# verify=False, # verify=False,
) as r: ) as r:
r.raise_for_status() r.raise_for_status()
+149 -63
View File
@@ -1,19 +1,22 @@
import base64
import hashlib
import logging import logging
import requests
import os import os
from typing import Annotated import requests
from fastapi import FastAPI, Header, HTTPException, status, Request from fastapi import FastAPI, Header, HTTPException, Request, status
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, RedirectResponse from fastapi.responses import FileResponse, RedirectResponse
from pydantic import BaseModel from pydantic import BaseModel
from typing import Annotated, Optional
from . import myice from . import myice
# OIDC Configuration # OIDC Configuration
CLIENT_ID = os.environ.get("CLIENT_ID", "8ea04fbb-4237-4b1d-a895-0b3575a3af3f") CLIENT_ID = os.environ.get("CLIENT_ID", "8ea04fbb-4237-4b1d-a895-0b3575a3af3f")
CLIENT_SECRET = os.environ.get( CLIENT_SECRET = os.environ.get("CLIENT_SECRET")
"CLIENT_SECRET", "REDACTED_CLIENT_SECRET" if not CLIENT_SECRET:
) raise RuntimeError("CLIENT_SECRET environment variable must be set")
REDIRECT_URI = os.environ.get("REDIRECT_URI", "http://localhost:8000/callback") REDIRECT_URI = os.environ.get("REDIRECT_URI", "http://localhost:8000/callback")
SECRET_KEY = os.environ.get("SECRET_KEY", "change_me_in_production")
AUTHORIZATION_ENDPOINT = "https://login.infomaniak.com/authorize" AUTHORIZATION_ENDPOINT = "https://login.infomaniak.com/authorize"
TOKEN_ENDPOINT = "https://login.infomaniak.com/token" TOKEN_ENDPOINT = "https://login.infomaniak.com/token"
USERINFO_ENDPOINT = "https://login.infomaniak.com/oauth2/userinfo" USERINFO_ENDPOINT = "https://login.infomaniak.com/oauth2/userinfo"
@@ -26,16 +29,19 @@ ALLOWED_USERS = (
else [] else []
) )
origins = ["*"] origins = (
os.environ.get("ALLOWED_ORIGINS", "").split(",")
if os.environ.get("ALLOWED_ORIGINS")
else []
)
app = FastAPI() app = FastAPI()
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=origins, allow_origins=origins,
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["GET", "POST"],
allow_headers=["*"], allow_headers=["Authorization", "Content-Type"],
) )
block_endpoints = ["/health"] block_endpoints = ["/health"]
@@ -70,9 +76,7 @@ class AuthHeaders(BaseModel):
# First check if it's a valid OIDC token # First check if it's a valid OIDC token
if self.validate_oidc_token(): if self.validate_oidc_token():
return True return True
# Fallback to the old static key for compatibility # No static key fallback; reject non-OIDC tokens
if token == "abc":
return True
return False return False
def validate_oidc_token(self) -> bool: def validate_oidc_token(self) -> bool:
@@ -81,39 +85,39 @@ class AuthHeaders(BaseModel):
if not token: if not token:
return False return False
# Basic validation - in production, you would validate the JWT signature # Try JWT validation first (for id_token-style tokens)
# and check issuer, audience, expiration, etc. if token.count(".") == 2:
import base64 import jwt
import json from jwt import PyJWKClient
# Decode JWT header and payload (without verification for simplicity in this example) jwks_client = PyJWKClient(JWKS_URI)
parts = token.split(".") signing_key = jwks_client.get_signing_key_from_jwt(token)
if len(parts) != 3: payload_data = jwt.decode(
# If not a standard JWT, treat as opaque token token,
# For now, we'll accept any non-empty token as valid for testing signing_key.key,
return len(token) > 0 algorithms=["RS256"],
audience=CLIENT_ID,
issuer="https://login.infomaniak.com/",
)
user_email = payload_data.get("email")
if ALLOWED_USERS and user_email and user_email not in ALLOWED_USERS:
return False
return True
# Decode payload (part 1) # Fallback: validate opaque tokens by calling userinfo endpoint
payload = parts[1] userinfo_response = requests.get(
if not payload: USERINFO_ENDPOINT, headers={"Authorization": f"Bearer {token}"}
)
if userinfo_response.status_code != 200:
return False return False
# Add padding if needed userinfo = userinfo_response.json()
payload += "=" * (4 - len(payload) % 4) user_email = userinfo.get("email")
decoded_payload = base64.urlsafe_b64decode(payload)
payload_data = json.loads(decoded_payload)
# Check if user is in allowed list
user_email = payload_data.get("email")
if ALLOWED_USERS and user_email and user_email not in ALLOWED_USERS: if ALLOWED_USERS and user_email and user_email not in ALLOWED_USERS:
return False return False
return True return True
except Exception as e: except Exception:
print(f"Token validation error: {e}") return False
# Even if we can't validate the token, if it exists, we'll accept it for now
# This is for development/testing purposes only
return len(self.token()) > 0
class HealthCheck(BaseModel): class HealthCheck(BaseModel):
@@ -136,21 +140,32 @@ def get_health() -> HealthCheck:
return HealthCheck(status="OK") return HealthCheck(status="OK")
@app.get("/style.css")
async def style_css():
return FileResponse("style.css")
@app.get("/favicon.ico") @app.get("/favicon.ico")
async def favico(): async def favico():
return FileResponse("favicon.ico") return FileResponse("favicon.ico")
@app.get("/login") @app.get("/login")
async def login(request: Request): async def login(request: Request, redirect_uri: Optional[str] = None):
"""Redirect to Infomaniak OIDC login""" """Redirect to Infomaniak OIDC login"""
import secrets
import urllib.parse import urllib.parse
state = "random_state_string" # In production, use a proper random state state = secrets.token_urlsafe(32)
nonce = "random_nonce_string" # In production, use a proper random nonce nonce = secrets.token_urlsafe(32)
# Store state and nonce in session (simplified for this example) # Generate PKCE parameters for public clients / Infomaniak requirement
# In production, use proper session management code_verifier = secrets.token_urlsafe(32)
code_challenge = (
base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode("ascii")).digest())
.decode("ascii")
.rstrip("=")
)
auth_url = ( auth_url = (
f"{AUTHORIZATION_ENDPOINT}" f"{AUTHORIZATION_ENDPOINT}"
@@ -160,14 +175,56 @@ async def login(request: Request):
f"&scope=openid email profile" f"&scope=openid email profile"
f"&state={state}" f"&state={state}"
f"&nonce={nonce}" f"&nonce={nonce}"
f"&code_challenge={code_challenge}"
f"&code_challenge_method=S256"
) )
return RedirectResponse(auth_url) response = RedirectResponse(auth_url)
# Store state and nonce in secure http-only cookies for validation on callback
response.set_cookie(
"oidc_state", state, httponly=True, secure=True, samesite="Lax", max_age=300
)
response.set_cookie(
"oidc_nonce", nonce, httponly=True, secure=True, samesite="Lax", max_age=300
)
response.set_cookie(
"oidc_verifier",
code_verifier,
httponly=True,
secure=True,
samesite="Lax",
max_age=300,
)
# Store target redirect URI (e.g. myice://callback) for mobile app flow
if redirect_uri:
response.set_cookie(
"oidc_target_redirect",
redirect_uri,
httponly=True,
secure=True,
samesite="Lax",
max_age=300,
)
return response
@app.get("/callback") @app.get("/callback")
async def callback(code: str, state: str): async def callback(request: Request, code: str, state: str):
"""Handle OIDC callback and exchange code for token""" """Handle OIDC callback and exchange code for token"""
expected_state = request.cookies.get("oidc_state")
code_verifier = request.cookies.get("oidc_verifier")
# Determine final redirect target (mobile app or web frontend)
target_redirect = request.cookies.get("oidc_target_redirect") or os.environ.get(
"FRONTEND_URL", "/"
)
# We should also delete state/nonce cookies once read
if not expected_state or state != expected_state:
response = RedirectResponse(f"/{target_redirect}?error=Invalid state parameter")
response.delete_cookie("oidc_state")
response.delete_cookie("oidc_nonce")
response.delete_cookie("oidc_verifier")
response.delete_cookie("oidc_target_redirect")
return response
# Exchange authorization code for access token # Exchange authorization code for access token
token_data = { token_data = {
"grant_type": "authorization_code", "grant_type": "authorization_code",
@@ -175,18 +232,24 @@ async def callback(code: str, state: str):
"client_secret": CLIENT_SECRET, "client_secret": CLIENT_SECRET,
"code": code, "code": code,
"redirect_uri": REDIRECT_URI, "redirect_uri": REDIRECT_URI,
"code_verifier": code_verifier,
} }
response = requests.post(TOKEN_ENDPOINT, data=token_data) token_response = requests.post(TOKEN_ENDPOINT, data=token_data).json()
token_response = response.json()
if "access_token" not in token_response: if "access_token" not in token_response:
# Log the error for debugging
logging.error("Token exchange failed: %s", token_response)
# Redirect back to frontend with error # Redirect back to frontend with error
frontend_url = os.environ.get("FRONTEND_URL", "/")
error_detail = token_response.get( error_detail = token_response.get(
"error_description", "Failed to obtain access token" "error_description", "Failed to obtain access token"
) )
return RedirectResponse(f"{frontend_url}?error={error_detail}") error_response = RedirectResponse(f"{target_redirect}?error={error_detail}")
error_response.delete_cookie("oidc_state")
error_response.delete_cookie("oidc_nonce")
error_response.delete_cookie("oidc_verifier")
error_response.delete_cookie("oidc_target_redirect")
return error_response
access_token = token_response["access_token"] access_token = token_response["access_token"]
@@ -201,17 +264,33 @@ async def callback(code: str, state: str):
user_email = userinfo.get("email") user_email = userinfo.get("email")
if ALLOWED_USERS and user_email not in ALLOWED_USERS: if ALLOWED_USERS and user_email not in ALLOWED_USERS:
# Redirect back to frontend with error # Redirect back to frontend with error
frontend_url = os.environ.get("FRONTEND_URL", "/") response = RedirectResponse(f"{target_redirect}?error=User not authorized")
return RedirectResponse(f"{frontend_url}?error=User not authorized") response.delete_cookie("oidc_state")
response.delete_cookie("oidc_nonce")
response.delete_cookie("oidc_verifier")
response.delete_cookie("oidc_target_redirect")
return response
# Redirect back to frontend with access token and user info # Redirect back to frontend with access token and user info
frontend_url = os.environ.get("FRONTEND_URL", "/") response = RedirectResponse(f"{target_redirect}#access_token={access_token}")
return RedirectResponse(f"{frontend_url}#access_token={access_token}") response.delete_cookie("oidc_state")
response.delete_cookie("oidc_nonce")
response.delete_cookie("oidc_verifier")
response.delete_cookie("oidc_target_redirect")
return response
@app.get("/exchange-token") @app.get("/exchange-token")
async def exchange_token(code: str): async def exchange_token(
request: Request,
code: str,
headers: Annotated[AuthHeaders, Header()],
):
"""Exchange authorization code for access token""" """Exchange authorization code for access token"""
if not headers.authorized():
raise HTTPException(401, detail="get out")
code_verifier = request.cookies.get("oidc_verifier")
# Exchange authorization code for access token # Exchange authorization code for access token
token_data = { token_data = {
"grant_type": "authorization_code", "grant_type": "authorization_code",
@@ -219,6 +298,7 @@ async def exchange_token(code: str):
"client_secret": CLIENT_SECRET, "client_secret": CLIENT_SECRET,
"code": code, "code": code,
"redirect_uri": REDIRECT_URI, "redirect_uri": REDIRECT_URI,
"code_verifier": code_verifier,
} }
response = requests.post(TOKEN_ENDPOINT, data=token_data) response = requests.post(TOKEN_ENDPOINT, data=token_data)
@@ -254,6 +334,9 @@ async def exchange_token(code: str):
@app.get("/userinfo") @app.get("/userinfo")
async def userinfo(headers: Annotated[AuthHeaders, Header()]): async def userinfo(headers: Annotated[AuthHeaders, Header()]):
"""Get user info from access token""" """Get user info from access token"""
if not headers.authorized():
raise HTTPException(401, detail="get out")
access_token = headers.token() access_token = headers.token()
userinfo_response = requests.get( userinfo_response = requests.get(
@@ -283,15 +366,15 @@ async def schedule(
try: try:
if existing_token: if existing_token:
myice.userdata = { user_data = {
"id": userid, "id": userid,
"id_club": club_id or 186, "id_club": club_id or 186,
"token": existing_token, "token": existing_token,
} }
else: else:
myice.userdata = myice.mobile_login(config_section=account) user_data = myice.mobile_login(config_section=account)
data = myice.refresh_data() data = myice.refresh_data(user_data=user_data)
if "club_games" in data: if "club_games" in data:
return data["club_games"] return data["club_games"]
else: else:
@@ -357,17 +440,20 @@ async def game(
game_id: int, game_id: int,
account: str = "default", account: str = "default",
): ):
if not headers.authorized():
raise HTTPException(401, detail="get out")
username, password, userid, existing_token, club_id = myice.get_login( username, password, userid, existing_token, club_id = myice.get_login(
config_section=account config_section=account
) )
if existing_token: if existing_token:
myice.userdata = { user_data = {
"id": userid, "id": userid,
"id_club": club_id or 186, "id_club": club_id or 186,
"token": existing_token, "token": existing_token,
} }
else: else:
myice.userdata = myice.mobile_login(config_section=account) user_data = myice.mobile_login(config_section=account)
# data = refresh_data() # data = refresh_data()
with requests.post( with requests.post(
@@ -375,11 +461,11 @@ async def game(
headers=myice.mobile_headers, headers=myice.mobile_headers,
data="&".join( data="&".join(
[ [
f"token={myice.userdata['token']}", f"token={user_data['token']}",
f"id_event={game_id}", f"id_event={game_id}",
"type=games", "type=games",
f"id_player={myice.userdata['id']}", f"id_player={user_data['id']}",
f"id_club={myice.userdata['id_club']}", f"id_club={user_data['id_club']}",
"language=FR", "language=FR",
] ]
), ),
+54
View File
@@ -0,0 +1,54 @@
# Flutter/Dart specific
.dart_tool/
.packages
build/
.pub/
# IDEs
.idea/
*.iml
*.ipr
*.iws
*.ctxt
# macOS
.DS_Store
# Android
*.apk
*.ap_
*.aab
!/android/app/src/main/AndroidManifest.xml
# iOS
ios/Pods/
ios/.symlinks/
ios/Flutter/Flutter.framework
ios/Flutter/Flutter.podspec
# Xcode
xcuserdata/
*.xcworkspace/
DerivedData/
# Web
lib/generated_plugin_registrant.dart
# Windows
windows/flutter/
windows/runner/
# Linux
linux/flutter/
linux/runner/
# Firebase
firebase_app_id_file.json
# Coverage
coverage/
# Temporary files
*.tmp
*.swp
*~
@@ -0,0 +1,33 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="myice_mobile"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myice" android:host="callback" />
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>MyIce</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>myice_mobile</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>ch.parano.myice</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myice</string>
</array>
</dict>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
+25
View File
@@ -0,0 +1,25 @@
---
name: myice_mobile
description: Flutter mobile app for MyIce
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.1.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0
provider: ^6.1.0
shared_preferences: ^2.2.0
flutter_web_auth: ^0.6.0
intl: ^0.19.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
Generated
+1528 -1026
View File
File diff suppressed because it is too large. Load diff
+7 -6
View File
@@ -1,20 +1,21 @@
[project] [project]
name = "myice" name = "myice"
version = "v0.5.6" version = "v0.6.1"
description = "myice parsing" description = "myice parsing"
authors = [ authors = [
{ name = "Rene Luria", "email" = "<rene@luria.ch>"}, { name = "Rene Luria", "email" = "<rene@luria.ch>"},
] ]
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.13"
dependencies = [ dependencies = [
"requests (>=2.32.3,<2.33.0)", "requests (>=2.32.3)",
"typer (>=0.15.1,<0.16.0)", "typer (>=0.15.1)",
"pypdf2 (>=3.0.1)", "pypdf (>=6.0.0)",
"rl-ai-tools >=1.9.0", "rl-ai-tools >=1.9.0",
"fastapi[standard] (>=0.115.11,<0.116.0)", "fastapi[standard] (>=0.115.11)",
"pyjwt[crypto] (>=2.10.1)",
] ]
[tool.poetry.dependencies] [tool.poetry.dependencies]
+47
View File
@@ -0,0 +1,47 @@
--extra-index-url https://pypi.purple.infomaniak.ch
annotated-doc==0.0.4 ; python_version >= "3.13"
annotated-types==0.7.0 ; python_version >= "3.13"
anyio==4.11.0 ; python_version >= "3.13"
certifi==2025.10.5 ; python_version >= "3.13"
charset-normalizer==3.4.4 ; python_version >= "3.13"
click==8.1.8 ; python_version >= "3.13"
colorama==0.4.6 ; (platform_system == "Windows" or sys_platform == "win32") and python_version >= "3.13"
dnspython==2.8.0 ; python_version >= "3.13"
email-validator==2.3.0 ; python_version >= "3.13"
fastapi-cli==0.0.16 ; python_version >= "3.13"
fastapi-cloud-cli==0.3.1 ; python_version >= "3.13"
fastapi==0.121.1 ; python_version >= "3.13"
h11==0.16.0 ; python_version >= "3.13"
httpcore==1.0.9 ; python_version >= "3.13"
httptools==0.7.1 ; python_version >= "3.13"
httpx==0.28.1 ; python_version >= "3.13"
idna==3.11 ; python_version >= "3.13"
jinja2==3.1.6 ; python_version >= "3.13"
markdown-it-py==4.0.0 ; python_version >= "3.13"
markupsafe==3.0.3 ; python_version >= "3.13"
mdurl==0.1.2 ; python_version >= "3.13"
pydantic-core==2.41.5 ; python_version >= "3.13"
pydantic==2.12.4 ; python_version >= "3.13"
pygments==2.19.2 ; python_version >= "3.13"
pypdf==6.2.0 ; python_version >= "3.13"
python-dotenv==1.2.1 ; python_version >= "3.13"
python-multipart==0.0.20 ; python_version >= "3.13"
pyyaml==6.0.3 ; python_version >= "3.13"
requests==2.32.5 ; python_version >= "3.13"
rich-toolkit==0.15.1 ; python_version >= "3.13"
rich==14.2.0 ; python_version >= "3.13"
rignore==0.7.6 ; python_version >= "3.13"
rl-ai-tools==1.15.0 ; python_version >= "3.13"
sentry-sdk==2.43.0 ; python_version >= "3.13"
shellingham==1.5.4 ; python_version >= "3.13"
sniffio==1.3.1 ; python_version >= "3.13"
starlette==0.49.3 ; python_version >= "3.13"
typer==0.15.4 ; python_version >= "3.13"
typing-extensions==4.15.0 ; python_version >= "3.13"
typing-inspection==0.4.2 ; python_version >= "3.13"
urllib3==2.5.0 ; python_version >= "3.13"
uvicorn==0.38.0 ; python_version >= "3.13"
uvloop==0.22.1 ; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.13"
watchfiles==1.1.1 ; python_version >= "3.13"
websockets==15.0.1 ; python_version >= "3.13"
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+8
View File
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html"],
theme: {
extend: {},
},
plugins: [],
}
Generated
+789
View File
@@ -0,0 +1,789 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.11.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
]
[[package]]
name = "certifi"
version = "2025.10.5"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.4"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
[[package]]
name = "click"
version = "8.1.8"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "dnspython"
version = "2.8.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
]
[[package]]
name = "email-validator"
version = "2.3.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "dnspython" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
]
[[package]]
name = "fastapi"
version = "0.119.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "pydantic" },
{ name = "starlette" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0a/f9/5c5bcce82a7997cc0eb8c47b7800f862f6b56adc40486ed246e5010d443b/fastapi-0.119.0.tar.gz", hash = "sha256:451082403a2c1f0b99c6bd57c09110ed5463856804c8078d38e5a1f1035dbbb7", size = 336756, upload-time = "2025-10-11T17:13:40.53Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/70/584c4d7cad80f5e833715c0a29962d7c93b4d18eed522a02981a6d1b6ee5/fastapi-0.119.0-py3-none-any.whl", hash = "sha256:90a2e49ed19515320abb864df570dd766be0662c5d577688f1600170f7f73cf2", size = 107095, upload-time = "2025-10-11T17:13:39.048Z" },
]
[package.optional-dependencies]
standard = [
{ name = "email-validator" },
{ name = "fastapi-cli", extra = ["standard"] },
{ name = "httpx" },
{ name = "jinja2" },
{ name = "python-multipart" },
{ name = "uvicorn", extra = ["standard"] },
]
[[package]]
name = "fastapi-cli"
version = "0.0.13"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "rich-toolkit" },
{ name = "typer" },
{ name = "uvicorn", extra = ["standard"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/32/4e/3f61850012473b097fc5297d681bd85788e186fadb8555b67baf4c7707f4/fastapi_cli-0.0.13.tar.gz", hash = "sha256:312addf3f57ba7139457cf0d345c03e2170cc5a034057488259c33cd7e494529", size = 17780, upload-time = "2025-09-20T16:37:31.089Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/36/7432750f3638324b055496d2c952000bea824259fca70df5577a6a3c172f/fastapi_cli-0.0.13-py3-none-any.whl", hash = "sha256:219b73ccfde7622559cef1d43197da928516acb4f21f2ec69128c4b90057baba", size = 11142, upload-time = "2025-09-20T16:37:29.695Z" },
]
[package.optional-dependencies]
standard = [
{ name = "fastapi-cloud-cli" },
{ name = "uvicorn", extra = ["standard"] },
]
[[package]]
name = "fastapi-cloud-cli"
version = "0.3.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "httpx" },
{ name = "pydantic", extra = ["email"] },
{ name = "rich-toolkit" },
{ name = "rignore" },
{ name = "sentry-sdk" },
{ name = "typer" },
{ name = "uvicorn", extra = ["standard"] },
]
sdist = { url = "https://files.pythonhosted.org/packages/f9/48/0f14d8555b750dc8c04382804e4214f1d7f55298127f3a0237ba566e69dd/fastapi_cloud_cli-0.3.1.tar.gz", hash = "sha256:8c7226c36e92e92d0c89827e8f56dbf164ab2de4444bd33aa26b6c3f7675db69", size = 24080, upload-time = "2025-10-09T11:32:58.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/79/7f5a5e5513e6a737e5fb089d9c59c74d4d24dc24d581d3aa519b326bedda/fastapi_cloud_cli-0.3.1-py3-none-any.whl", hash = "sha256:7d1a98a77791a9d0757886b2ffbf11bcc6b3be93210dd15064be10b216bf7e00", size = 19711, upload-time = "2025-10-09T11:32:57.118Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httptools"
version = "0.7.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" },
{ url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" },
{ url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" },
{ url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" },
{ url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" },
{ url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" },
{ url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" },
{ url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" },
{ url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" },
{ url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" },
{ url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" },
{ url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" },
{ url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" },
{ url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.3"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "myice"
version = "0.5.8"
source = { editable = "." }
dependencies = [
{ name = "fastapi", extra = ["standard"] },
{ name = "pypdf" },
{ name = "requests" },
{ name = "rl-ai-tools" },
{ name = "typer" },
]
[package.metadata]
requires-dist = [
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.11" },
{ name = "pypdf", specifier = ">=6.0.0" },
{ name = "requests", specifier = ">=2.32.3" },
{ name = "rl-ai-tools", specifier = ">=1.9.0" },
{ name = "typer", specifier = ">=0.15.1" },
]
[[package]]
name = "pydantic"
version = "2.12.2"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8d/35/d319ed522433215526689bad428a94058b6dd12190ce7ddd78618ac14b28/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd", size = 816358, upload-time = "2025-10-14T15:02:21.842Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/98/468cb649f208a6f1279448e6e5247b37ae79cf5e4041186f1e2ef3d16345/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae", size = 460628, upload-time = "2025-10-14T15:02:19.623Z" },
]
[package.optional-dependencies]
email = [
{ name = "email-validator" },
]
[[package]]
name = "pydantic-core"
version = "2.41.4"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" },
{ url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" },
{ url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" },
{ url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" },
{ url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" },
{ url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" },
{ url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" },
{ url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" },
{ url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" },
{ url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" },
{ url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" },
{ url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" },
{ url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" },
{ url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" },
{ url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" },
{ url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" },
{ url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" },
{ url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" },
{ url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" },
{ url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" },
{ url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" },
{ url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" },
{ url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" },
{ url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" },
{ url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" },
{ url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" },
{ url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" },
{ url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" },
{ url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" },
{ url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" },
{ url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" },
{ url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" },
{ url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" },
{ url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" },
{ url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" },
{ url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pypdf"
version = "6.1.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/a6/85/4c0f12616db83c2e3ef580c3cfa98bd082e88fc8d02e136bad3bede1e3fa/pypdf-6.1.1.tar.gz", hash = "sha256:10f44d49bf2a82e54c3c5ba3cdcbb118f2a44fc57df8ce51d6fb9b1ed9bfbe8b", size = 5074507, upload-time = "2025-09-28T13:29:16.165Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/07/ed/adae13756d9dabdddee483fc7712905bb5585fbf6e922b1a19aca3a29cd1/pypdf-6.1.1-py3-none-any.whl", hash = "sha256:7781f99493208a37a7d4275601d883e19af24e62a525c25844d22157c2e4cde7", size = 323455, upload-time = "2025-09-28T13:29:14.392Z" },
]
[[package]]
name = "python-dotenv"
version = "1.1.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
]
[[package]]
name = "python-multipart"
version = "0.0.20"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.3"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "rich"
version = "14.2.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
]
[[package]]
name = "rich-toolkit"
version = "0.15.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/67/33/1a18839aaa8feef7983590c05c22c9c09d245ada6017d118325bbfcc7651/rich_toolkit-0.15.1.tar.gz", hash = "sha256:6f9630eb29f3843d19d48c3bd5706a086d36d62016687f9d0efa027ddc2dd08a", size = 115322, upload-time = "2025-09-04T09:28:11.789Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/49/42821d55ead7b5a87c8d121edf323cb393d8579f63e933002ade900b784f/rich_toolkit-0.15.1-py3-none-any.whl", hash = "sha256:36a0b1d9a135d26776e4b78f1d5c2655da6e0ef432380b5c6b523c8d8ab97478", size = 29412, upload-time = "2025-09-04T09:28:10.587Z" },
]
[[package]]
name = "rignore"
version = "0.7.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/ae/46/e5ef3423a3746f91d3a3d9a68c499fde983be7dbab7d874efa8d3bb139ba/rignore-0.7.0.tar.gz", hash = "sha256:cfe6a2cbec855b440d7550d53e670246fce43ca5847e46557b6d4577c9cdb540", size = 12796, upload-time = "2025-10-02T13:26:22.194Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/85/cd1441043c5ed13e671153af260c5f328042ebfb87aa28849367602206f2/rignore-0.7.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:190e469db68112c4027a7a126facfd80ce353374ff208c585ca7dacc75de0472", size = 880474, upload-time = "2025-10-02T13:25:08.111Z" },
{ url = "https://files.pythonhosted.org/packages/f4/07/d5b9593cb05593718508308543a8fbee75998a7489cf4f4b489d2632bd4a/rignore-0.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0a43f6fabf46ed8e96fbf2861187362e513960c2a8200c35242981bd36ef8b96", size = 811882, upload-time = "2025-10-02T13:24:56.599Z" },
{ url = "https://files.pythonhosted.org/packages/aa/67/b82b2704660c280061d8bc90bc91092622309f78e20c9e3321f45f88cd4e/rignore-0.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b89a59e5291805eca3c3317a55fcd2a579e9ee1184511660078a398182463deb", size = 892043, upload-time = "2025-10-02T13:23:22.326Z" },
{ url = "https://files.pythonhosted.org/packages/8b/7e/e91a1899a06882cd8a7acc3025c51b9f830971b193bd6b72e34254ed7733/rignore-0.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a155f36be847c05c800e0218e9ac04946ba44bf077e1f11dc024ca9e1f7a727", size = 865404, upload-time = "2025-10-02T13:23:40.085Z" },
{ url = "https://files.pythonhosted.org/packages/91/2c/68487538a2d2d7e0e1ca1051d143af690211314e22cbed58a245e816ebaf/rignore-0.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dba075135ac3cda5f3236b4f03f82bbcd97454a908631ad3da93aae1e7390b17", size = 1167661, upload-time = "2025-10-02T13:23:57.578Z" },
{ url = "https://files.pythonhosted.org/packages/b4/39/8498ac13fb710a1920526480f9476aaeaaaa20c522a027d07513929ba9d9/rignore-0.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8525b8c31f36dc9fbcb474ef58d654f6404b19b6110b7f5df332e58e657a4aa8", size = 936272, upload-time = "2025-10-02T13:24:13.414Z" },
{ url = "https://files.pythonhosted.org/packages/55/1a/38b92fde209931611dcff0db59bd5656a325ba58d368d4e50f1e711fdd16/rignore-0.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0428b64d8b02ad83fc0a2505ded0e9064cac97df7aa1dffc9c7558b56429912", size = 950552, upload-time = "2025-10-02T13:24:43.263Z" },
{ url = "https://files.pythonhosted.org/packages/e3/01/f59f38ae1b879309b0151b1ed0dd82880e1d3759f91bfdaa570730672308/rignore-0.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab1db960a64835ec3ed541951821bfc38f30dfbd6ebd990f7d039d0c54ff957", size = 974407, upload-time = "2025-10-02T13:24:30.618Z" },
{ url = "https://files.pythonhosted.org/packages/6e/67/de92fdc09dc1a622abb6d1b2678e940d24de2a07c60d193126eb52a7e8ea/rignore-0.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3749711b1e50fb5b28b55784e159a3b8209ecc72d01cc1511c05bc3a23b4a063", size = 1072865, upload-time = "2025-10-02T13:25:20.451Z" },
{ url = "https://files.pythonhosted.org/packages/65/bb/75fbef03cf56b0918880cb3b922da83d6546309566be60f6c6b451f7221b/rignore-0.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:57240739c786f897f89e29c05e529291ee1b477df9f6b29b774403a23a169fe2", size = 1129007, upload-time = "2025-10-02T13:25:36.837Z" },
{ url = "https://files.pythonhosted.org/packages/ec/24/4d591d45a8994fb4afaefa22e356d69948726c9ccba0cfd76c82509aedc2/rignore-0.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6b70581286acd5f96ce11efd209bfe9261108586e1a948cc558fc3f58ba5bf5f", size = 1106827, upload-time = "2025-10-02T13:25:52.964Z" },
{ url = "https://files.pythonhosted.org/packages/c2/b3/b614d54fa1f1c7621aeb20b2841cd980288ad9d7d61407fc4595d5c5f132/rignore-0.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33fb6e4cba1b798f1328e889b4bf2341894d82e3be42bb3513b4e0fe38788538", size = 1115328, upload-time = "2025-10-02T13:26:10.947Z" },
{ url = "https://files.pythonhosted.org/packages/83/22/ea0b3e30e230b2d2222e1ee18e20316c8297088f4cc6a6ea2ee6cb34f595/rignore-0.7.0-cp313-cp313-win32.whl", hash = "sha256:119f0497fb4776cddc663ee8f35085ce00758bd423221ba1e8222a816e10cf5e", size = 636896, upload-time = "2025-10-02T13:26:40.3Z" },
{ url = "https://files.pythonhosted.org/packages/79/16/f55b3db13f6fff408fde348d2a726d3b4ba06ed55dce8ff119e374ce3005/rignore-0.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:fb06e11dda689be138909f53639f0baa8d7c6be4d76ca9ec316382ccf3517469", size = 716519, upload-time = "2025-10-02T13:26:28.51Z" },
{ url = "https://files.pythonhosted.org/packages/69/db/8c20a7b59abb21d3d20d387656b6759cd5890fa68185064fe8899f942a4b/rignore-0.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f2255821ab4bc34fa129a94535f5d0d88b164940b25d0a3b26ebd41d99f1a9f", size = 890684, upload-time = "2025-10-02T13:23:23.761Z" },
{ url = "https://files.pythonhosted.org/packages/45/a0/ae5ca63aed23f64dcd740f55ee6432037af5c09d25efaf79dc052a4a51ff/rignore-0.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b57efcbbc1510f8ce831a5e19fb1fe9dd329bb246c4e4f8a09bf1c06687b0331", size = 865174, upload-time = "2025-10-02T13:23:41.948Z" },
{ url = "https://files.pythonhosted.org/packages/ae/27/5aff661e792efbffda689f0d3fa91ea36f2e0d4bcca3b02f70ae95ea96da/rignore-0.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ead4bc2baceeccdfeb82cb70ba8f70fdb6dc1e58976f805f9d0d19b9ee915f0", size = 1165293, upload-time = "2025-10-02T13:23:59.238Z" },
{ url = "https://files.pythonhosted.org/packages/cb/df/13de7ce5ba2a58c724ef202310408729941c262179389df5e90cb9a41381/rignore-0.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f0a8996437a22df0faf2844d65ec91d41176b9d4e7357abee42baa39dc996ae", size = 936093, upload-time = "2025-10-02T13:24:15.057Z" },
{ url = "https://files.pythonhosted.org/packages/c3/63/4ea42bc454db8499906c8d075a7a0053b7fd381b85f3bcc857e68a8b8b23/rignore-0.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cb17ef4a413444fccbd57e1b4a3870f1320951b81f1b7007af9c70e1a5bc2897", size = 1071518, upload-time = "2025-10-02T13:25:22.076Z" },
{ url = "https://files.pythonhosted.org/packages/a3/a7/7400a4343d1b5a1345a98846c6fd7768ff13890d207fce79d690c7fd7798/rignore-0.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:b12b316adf6cf64f9d22bd690b2aa019a37335a1f632a0da7fb15a423cb64080", size = 1128403, upload-time = "2025-10-02T13:25:38.394Z" },
{ url = "https://files.pythonhosted.org/packages/45/8b/ce8ff27336a86bad47bbf011f8f7fb0b82b559ee4a0d6a4815ee3555ef56/rignore-0.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:dba8181d999387c17dd6cce5fd7f0009376ca8623d2d86842d034b18d83dc768", size = 1105552, upload-time = "2025-10-02T13:25:54.511Z" },
{ url = "https://files.pythonhosted.org/packages/8c/e2/7925b564d853c7057f150a7f2f384400422ed30f7b7baf2fde5849562381/rignore-0.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:04a3d4513cdd184f4f849ae8d6407a169cca543a2c4dd69bfc42e67cb0155504", size = 1114826, upload-time = "2025-10-02T13:26:12.56Z" },
{ url = "https://files.pythonhosted.org/packages/c4/34/c42ccdd81143d38d99e45b965e4040a1ef6c07a365ad205dd94b6d16c794/rignore-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:a296bc26b713aacd0f31702e7d89426ba6240abdbf01b2b18daeeaeaa782f475", size = 879718, upload-time = "2025-10-02T13:25:09.62Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ba/f522adf949d2b581a0a1e488a79577631ed6661fdc12e80d4182ed655036/rignore-0.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7f71807ed0bc1542860a8fa1615a0d93f3d5a22dde1066e9f50d7270bc60686", size = 810391, upload-time = "2025-10-02T13:24:58.144Z" },
{ url = "https://files.pythonhosted.org/packages/f2/82/935bffa4ad7d9560541daaca7ba0e4ee9b0b9a6370ab9518cf9c991087bb/rignore-0.7.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e6ff54399ddb650f4e4dc74b325766e7607967a49b868326e9687fc3642620", size = 950261, upload-time = "2025-10-02T13:24:45.121Z" },
{ url = "https://files.pythonhosted.org/packages/1e/0e/22abda23cc6d20901262fcfea50c25ed66ca6e1a5dc610d338df4ca10407/rignore-0.7.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09dfad3ca450b3967533c6b1a2c7c0228c63c518f619ff342df5f9c3ed978b66", size = 974258, upload-time = "2025-10-02T13:24:32.44Z" },
{ url = "https://files.pythonhosted.org/packages/ed/8d/0ba2c712723fdda62125087d00dcdad93102876d4e3fa5adbb99f0b859c3/rignore-0.7.0-cp314-cp314-win32.whl", hash = "sha256:2850718cfb1caece6b7ac19a524c7905a8d0c6627b0d0f4e81798e20b6c75078", size = 637403, upload-time = "2025-10-02T13:26:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/1c/63/0d7df1237c6353d1a85d8a0bc1797ac766c68e8bc6fbca241db74124eb61/rignore-0.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2401637dc8ab074f5e642295f8225d2572db395ae504ffc272a8d21e9fe77b2c", size = 717404, upload-time = "2025-10-02T13:26:29.936Z" },
]
[[package]]
name = "rl-ai-tools"
version = "1.15.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "requests" },
{ name = "typer" },
]
sdist = { url = "https://pypi.purple.infomaniak.ch/packages/rl_ai_tools-1.15.0.tar.gz", hash = "sha256:77e62b0e27b6a6fea28a53af406236a4af389cf05bcdb3a5c8fd5dbced90548b" }
wheels = [
{ url = "https://pypi.purple.infomaniak.ch/packages/rl_ai_tools-1.15.0-py3-none-any.whl", hash = "sha256:6534b6343d9666a7c394ad787efc513b3dc019eccb23845fc700ff861e0190bc" },
]
[[package]]
name = "sentry-sdk"
version = "2.42.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/b2/7481156cf42b7f66cffb371e504b7ace12b4f016b8872ffcf0873ae9534b/sentry_sdk-2.42.0.tar.gz", hash = "sha256:91c69c9372fb5fb4df0ac39456ccf7286f0428b3ee1cdd389f9dd36c04e0f5c9", size = 351242, upload-time = "2025-10-15T07:41:15.577Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/4a/9810a246ec5d1df2ae066efefeecfa91d3c548fa2bd5390184e016112887/sentry_sdk-2.42.0-py2.py3-none-any.whl", hash = "sha256:1a7986e638306ff158f52dd47d9480a4055e6c289388caa90628acb2563fe7bd", size = 379496, upload-time = "2025-10-15T07:41:13.802Z" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "starlette"
version = "0.48.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
]
[[package]]
name = "typer"
version = "0.15.4"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6c/89/c527e6c848739be8ceb5c44eb8208c52ea3515c6cf6406aa61932887bf58/typer-0.15.4.tar.gz", hash = "sha256:89507b104f9b6a0730354f27c39fae5b63ccd0c95b1ce1f1a6ba0cfd329997c3", size = 101559, upload-time = "2025-05-14T16:34:57.704Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/62/d4ba7afe2096d5659ec3db8b15d8665bdcb92a3c6ff0b95e99895b335a9c/typer-0.15.4-py3-none-any.whl", hash = "sha256:eb0651654dcdea706780c466cf06d8f174405a659ffff8f163cfbfee98c0e173", size = 45258, upload-time = "2025-05-14T16:34:55.583Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]]
name = "urllib3"
version = "2.5.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
]
[[package]]
name = "uvicorn"
version = "0.37.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" },
]
[package.optional-dependencies]
standard = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "httptools" },
{ name = "python-dotenv" },
{ name = "pyyaml" },
{ name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
{ name = "watchfiles" },
{ name = "websockets" },
]
[[package]]
name = "uvloop"
version = "0.21.0"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" },
{ url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" },
{ url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" },
{ url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" },
{ url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" },
{ url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" },
]
[[package]]
name = "watchfiles"
version = "1.1.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" },
{ url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" },
{ url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" },
{ url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" },
{ url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" },
{ url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" },
{ url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" },
{ url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" },
{ url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" },
{ url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" },
{ url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" },
{ url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" },
{ url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" },
{ url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" },
{ url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" },
{ url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" },
{ url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" },
{ url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" },
{ url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" },
{ url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" },
{ url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" },
{ url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" },
{ url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" },
{ url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" },
{ url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" },
{ url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" },
{ url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" },
{ url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" },
{ url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" },
{ url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" },
{ url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" },
{ url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" },
{ url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" },
{ url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" },
{ url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" },
{ url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" },
{ url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" },
{ url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" },
{ url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" },
{ url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" },
{ url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" },
{ url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" },
{ url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" },
{ url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" },
{ url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" },
]
[[package]]
name = "websockets"
version = "15.0.1"
source = { registry = "https://pypi.purple.infomaniak.ch/" }
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
{ url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
{ url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
{ url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
{ url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
{ url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
{ url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
{ url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
{ url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
]