38 Commits
Author SHA1 Message Date
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
herel 1941a924e2 chore: bump version to v0.5.6 2025-08-20 10:17:08 +02:00
herel 702c425476 feat: add secondary agegroup selector for subgroup filtering
- Added subgroup dropdown selector to filter events by team within agegroup
- Implemented logic to populate subgroup options based on selected agegroup
- Updated event filtering to support both agegroup and subgroup criteria
- Added event listeners for real-time filtering when selectors change
2025-08-20 10:17:00 +02:00
herel a74da811c1 chore: bump version to v0.5.5 2025-08-19 19:22:21 +02:00
herel dac110cee0 feat: auto-load events on login and improve player display in event details
- Automatically load events when user logs in
- Rename updateAccountOptions to updateAccountOptionsAndLoadEvents
- Add auto-fetch of events after account selection
- Enhance event details modal with:
  * Player count summary
  * Position breakdown
  * Players sorted by position first, then by number
  * Position displayed in player list
2025-08-19 19:21:50 +02:00
herel 0fd58848f2 chore: add event title 2025-08-19 19:12:45 +02:00
herel de222023a8 chore: bump version to v0.5.4 2025-08-19 19:06:47 +02:00
herel 08aa3f6a0e feat: update agegroup filter to show only agegroup and filter accordingly 2025-08-19 19:06:24 +02:00
herel 0e02245f21 chore: bump version to v0.5.3 2025-08-19 18:50:12 +02:00
herel a86cf76228 fix: improve UI layout and add loading indicator for events 2025-08-19 18:48:56 +02:00
herel 3ab3273fba fix: finalize UI improvements and ensure all functionality works
Final adjustments to UI layout and positioning. Ensured all JavaScript functions are properly connected and working. Verified event loading functionality is fully restored.
2025-08-19 16:12:51 +02:00
herel a17467c313 fix: restore event loading functionality and improve UI layout
Fixed incomplete fetchEvents function by adding missing data processing logic. Restored updateAgeGroupOptions, displayEvents, and fetchEventDetails functions. Moved connected user info to top-right corner. Hide title when logged in. Reduced vertical spacing for better layout. Made event filters more compact. All functionality restored and UI improved.
2025-08-19 16:09:57 +02:00
herel 9c6df3f6a2 feat: enhance login button styling and fix JavaScript syntax errors
Centered the login button on the page with improved styling including padding and shadow effects. Fixed JavaScript syntax errors in the event handling code. Removed unmatched closing div tags to ensure valid HTML structure.
2025-08-19 15:49:28 +02:00
herel a17065b280 fix: improve user authentication and account handling
Fixed issues with user display by fetching user info from userinfo endpoint

Improved error handling for JSON responses in schedule endpoint

Fixed account selection to use available accounts from config instead of default

Enhanced frontend to properly handle API responses and errors
2025-08-19 15:17:45 +02:00
herel 5f12fb08b8 fix: improve authentication handling and error management 2025-08-19 15:04:53 +02:00
herel a2e44b6f50 feat: implement OpenID Connect authentication with Infomaniak 2025-08-19 11:16:07 +02:00
herel 15c148e233 chore: bump version to v0.5.2 2025-08-19 10:48:55 +02:00
herel 7b6bf91502 fix: escape control characters in JSON output to make it jq compatible 2025-08-19 10:48:04 +02:00
herel c5863ebc5f chore: bump version to v0.5.1 2025-08-19 09:41:57 +02:00
herel 6c5668d608 fix: improve JSON response handling in API calls 2025-08-19 09:41:28 +02:00
herel 0c62fcaa01 feat: make config_section a global CLI option
Users can now specify --config-section once at the beginning of the command instead of repeating it for each subcommand. Also bumped version to v0.5.0 for this new feature.
2025-08-19 09:25:50 +02:00
herel bec33d0545 chore: display userid after fetch 2025-08-19 09:10:04 +02:00
herel c602727f08 fix: correct project scripts section name and bump version
- Changed [projectscripts] to [project.scripts] in pyproject.toml to properly register the myice command line tool
- Bump version to v0.4.3
2025-08-19 09:03:42 +02:00
herel 6be182a894 fix: resolve typer/click compatibility issue with make_metavar error
Fixed TypeError: Parameter.make_metavar() missing 1 required positional argument: 'ctx'
by correcting typer.Option(...) usage for optional parameters with default None values.

Bump version to v0.4.2
2025-08-19 08:57:37 +02:00
herel fafefd3085 chore: bump version to v0.4.1 and update dependencies 2025-08-19 08:52:39 +02:00
5 changed files with 70 additions and 244 deletions

No files matched your search

+4 -15
View File
@@ -475,17 +475,6 @@
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) {
// Reset subgroup options
subgroupSelect.innerHTML = '<option value="">Tous</option>';
@@ -504,9 +493,10 @@
events
.filter(event => event.agegroup === selectedAgegroup)
.forEach(event => {
// Extract subgroup from event.name by removing opponent
// Extract subgroup from event.name or event.title
// This assumes the subgroup is part of the name field
if (event.name && event.name !== selectedAgegroup) {
subgroups.add(extractSubgroup(event.name));
subgroups.add(event.name);
}
});
@@ -531,8 +521,7 @@
if (selectedAgegroup !== "" && event.agegroup !== selectedAgegroup) return false;
// Filter by subgroup
let eventSubgroup = extractSubgroup(event.name);
if (selectedSubgroup !== "" && eventSubgroup !== selectedSubgroup) return false;
if (selectedSubgroup !== "" && event.name !== selectedSubgroup) return false;
return true;
});
+7 -36
View File
@@ -140,14 +140,14 @@ class AgeGroup(str, Enum):
u13a = "U13 (A)"
u13p = "U13 (Prép)"
u14ev = "U14 (Elite Vernets)"
u14sae = "U14 (Groupe SAE)"
u14esmv = "U14 (Elite Sous-Moulin/Vergers)"
u15t = "U15 (Top)"
u15a = "U15 (A)"
u16e = "U16 (Elit)"
u16e = "U16 (Elite)"
u16t = "U16 (Top GS Ass)"
u18e = "U18 (Elit)"
u21e = "U21 (Elit)"
u18e = "U18 (Elite)"
u18el = "U18 (Elit)"
u21e = "U21 (ELIT)"
u14t = "U14 (Top)"
u14t1 = "U14 (Top1)"
u14t2 = "U14 (Top2)"
@@ -334,7 +334,7 @@ def get_schedule(num_days: int) -> str:
global userid
assert session and userid
now = datetime.datetime.now()
date_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
date_start = now
date_end = date_start + datetime.timedelta(days=num_days)
r = session.post(
"https://app.myice.hockey/inc/processclubplanning.php",
@@ -418,13 +418,11 @@ def schedule(
def os_open(file: str) -> None:
import subprocess
if os.uname().sysname == "Linux":
subprocess.run(["xdg-open", file])
os.system(f"xdg-open {file}")
else:
print(f"Opening file {file}", file=sys.stderr)
subprocess.run(["open", file])
os.system(f"open {file}")
def extract_players(pdf_file: Path) -> List[str]:
@@ -594,18 +592,6 @@ 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):
global global_config_section
import base64
@@ -618,13 +604,10 @@ def mobile_login(config_section: str | None = None):
return {"id": 0, "token": token, "id_club": club_id}
print("Requesting token", file=sys.stderr)
# Print curl equivalent for debugging
print(curl_for_mobile_login(username, password), file=sys.stderr)
with requests.post(
"https://app.myice.hockey/api/mobilerest/login",
headers=mobile_headers,
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,
) as r:
r.raise_for_status()
@@ -643,19 +626,7 @@ userdata = {
}
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():
# Print curl equivalent for debugging
print(
curl_for_refresh_data(userdata["token"], userdata["id_club"]), file=sys.stderr
)
with requests.post(
"https://app.myice.hockey/api/mobilerest/refreshdata",
headers=mobile_headers,
+52 -105
View File
@@ -1,22 +1,19 @@
import base64
import hashlib
import logging
import os
import requests
from fastapi import FastAPI, Header, HTTPException, Request, status
import os
from typing import Annotated
from fastapi import FastAPI, Header, HTTPException, status, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, RedirectResponse
from pydantic import BaseModel
from typing import Annotated
from . import myice
# OIDC Configuration
CLIENT_ID = os.environ.get("CLIENT_ID", "8ea04fbb-4237-4b1d-a895-0b3575a3af3f")
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")
if not CLIENT_SECRET:
raise RuntimeError("CLIENT_SECRET environment variable must be set")
CLIENT_SECRET = os.environ.get(
"CLIENT_SECRET", "REDACTED_CLIENT_SECRET"
)
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"
TOKEN_ENDPOINT = "https://login.infomaniak.com/token"
USERINFO_ENDPOINT = "https://login.infomaniak.com/oauth2/userinfo"
@@ -29,19 +26,16 @@ ALLOWED_USERS = (
else []
)
origins = (
os.environ.get("ALLOWED_ORIGINS", "").split(",")
if os.environ.get("ALLOWED_ORIGINS")
else []
)
origins = ["*"]
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
allow_methods=["*"],
allow_headers=["*"],
)
block_endpoints = ["/health"]
@@ -76,7 +70,9 @@ class AuthHeaders(BaseModel):
# First check if it's a valid OIDC token
if self.validate_oidc_token():
return True
# No static key fallback; reject non-OIDC tokens
# Fallback to the old static key for compatibility
if token == "abc":
return True
return False
def validate_oidc_token(self) -> bool:
@@ -85,39 +81,39 @@ class AuthHeaders(BaseModel):
if not token:
return False
# Try JWT validation first (for id_token-style tokens)
if token.count(".") == 2:
import jwt
from jwt import PyJWKClient
# Basic validation - in production, you would validate the JWT signature
# and check issuer, audience, expiration, etc.
import base64
import json
jwks_client = PyJWKClient(JWKS_URI)
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload_data = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
audience=CLIENT_ID,
issuer="https://login.infomaniak.com/",
)
# Decode JWT header and payload (without verification for simplicity in this example)
parts = token.split(".")
if len(parts) != 3:
# If not a standard JWT, treat as opaque token
# For now, we'll accept any non-empty token as valid for testing
return len(token) > 0
# Decode payload (part 1)
payload = parts[1]
if not payload:
return False
# Add padding if needed
payload += "=" * (4 - len(payload) % 4)
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:
return False
return True
# Fallback: validate opaque tokens by calling userinfo endpoint
userinfo_response = requests.get(
USERINFO_ENDPOINT, headers={"Authorization": f"Bearer {token}"}
)
if userinfo_response.status_code != 200:
return False
userinfo = userinfo_response.json()
user_email = userinfo.get("email")
if ALLOWED_USERS and user_email and user_email not in ALLOWED_USERS:
return False
return True
except Exception:
return False
except Exception as e:
print(f"Token validation error: {e}")
# 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):
@@ -150,18 +146,11 @@ async def login(request: Request):
"""Redirect to Infomaniak OIDC login"""
import urllib.parse
import secrets
state = "random_state_string" # In production, use a proper random state
nonce = "random_nonce_string" # In production, use a proper random nonce
state = secrets.token_urlsafe(32)
nonce = secrets.token_urlsafe(32)
# Generate PKCE parameters for public clients / Infomaniak requirement
code_verifier = secrets.token_urlsafe(32)
code_challenge = (
base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode("ascii")).digest())
.decode("ascii")
.rstrip("=")
)
# Store state and nonce in session (simplified for this example)
# In production, use proper session management
auth_url = (
f"{AUTHORIZATION_ENDPOINT}"
@@ -171,30 +160,14 @@ async def login(request: Request):
f"&scope=openid email profile"
f"&state={state}"
f"&nonce={nonce}"
f"&code_challenge={code_challenge}"
f"&code_challenge_method=S256"
)
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, max_age=300)
response.set_cookie("oidc_nonce", nonce, httponly=True, max_age=300)
response.set_cookie("oidc_verifier", code_verifier, httponly=True, max_age=300)
return response
return RedirectResponse(auth_url)
@app.get("/callback")
async def callback(request: Request, code: str, state: str):
async def callback(code: str, state: str):
"""Handle OIDC callback and exchange code for token"""
expected_state = request.cookies.get("oidc_state")
code_verifier = request.cookies.get("oidc_verifier")
# We should also delete state/nonce cookies once read
if not expected_state or state != expected_state:
response = RedirectResponse("/?error=Invalid state parameter")
response.delete_cookie("oidc_state")
response.delete_cookie("oidc_nonce")
response.delete_cookie("oidc_verifier")
return response
# Exchange authorization code for access token
token_data = {
"grant_type": "authorization_code",
@@ -202,24 +175,18 @@ async def callback(request: Request, code: str, state: str):
"client_secret": CLIENT_SECRET,
"code": code,
"redirect_uri": REDIRECT_URI,
"code_verifier": code_verifier,
}
token_response = requests.post(TOKEN_ENDPOINT, data=token_data).json()
response = requests.post(TOKEN_ENDPOINT, data=token_data)
token_response = response.json()
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
frontend_url = os.environ.get("FRONTEND_URL", "/")
error_detail = token_response.get(
"error_description", "Failed to obtain access token"
)
error_response = RedirectResponse(f"{frontend_url}?error={error_detail}")
error_response.delete_cookie("oidc_state")
error_response.delete_cookie("oidc_nonce")
error_response.delete_cookie("oidc_verifier")
return error_response
return RedirectResponse(f"{frontend_url}?error={error_detail}")
access_token = token_response["access_token"]
@@ -235,30 +202,16 @@ async def callback(request: Request, code: str, state: str):
if ALLOWED_USERS and user_email not in ALLOWED_USERS:
# Redirect back to frontend with error
frontend_url = os.environ.get("FRONTEND_URL", "/")
response = RedirectResponse(f"{frontend_url}?error=User not authorized")
response.delete_cookie("oidc_state")
response.delete_cookie("oidc_nonce")
response.delete_cookie("oidc_verifier")
return response
return RedirectResponse(f"{frontend_url}?error=User not authorized")
# Redirect back to frontend with access token and user info
frontend_url = os.environ.get("FRONTEND_URL", "/")
response = RedirectResponse(f"{frontend_url}#access_token={access_token}")
response.delete_cookie("oidc_state")
response.delete_cookie("oidc_nonce")
response.delete_cookie("oidc_verifier")
return response
return RedirectResponse(f"{frontend_url}#access_token={access_token}")
@app.get("/exchange-token")
async def exchange_token(
code: str,
headers: Annotated[AuthHeaders, Header()],
):
async def exchange_token(code: str):
"""Exchange authorization code for access token"""
if not headers.authorized():
raise HTTPException(401, detail="get out")
# Exchange authorization code for access token
token_data = {
"grant_type": "authorization_code",
@@ -301,9 +254,6 @@ async def exchange_token(
@app.get("/userinfo")
async def userinfo(headers: Annotated[AuthHeaders, Header()]):
"""Get user info from access token"""
if not headers.authorized():
raise HTTPException(401, detail="get out")
access_token = headers.token()
userinfo_response = requests.get(
@@ -407,9 +357,6 @@ async def game(
game_id: int,
account: str = "default",
):
if not headers.authorized():
raise HTTPException(401, detail="get out")
username, password, userid, existing_token, club_id = myice.get_login(
config_section=account
)
Generated
+6 -86
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "annotated-doc"
@@ -90,7 +90,8 @@ version = "2.0.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
groups = ["dev"]
markers = "implementation_name == \"pypy\""
files = [
{file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"},
{file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"},
@@ -177,7 +178,6 @@ files = [
{file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"},
{file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"},
]
markers = {main = "platform_python_implementation != \"PyPy\"", dev = "implementation_name == \"pypy\""}
[package.dependencies]
pycparser = {version = "*", markers = "implementation_name != \"PyPy\""}
@@ -348,68 +348,6 @@ files = [
[package.extras]
test = ["pytest"]
[[package]]
name = "cryptography"
version = "49.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = "!=3.9.0,!=3.9.1,>=3.9"
groups = ["main"]
files = [
{file = "cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68"},
{file = "cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9"},
{file = "cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f"},
{file = "cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459"},
{file = "cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e"},
{file = "cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866"},
{file = "cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8"},
{file = "cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3"},
{file = "cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27"},
{file = "cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61"},
{file = "cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8"},
{file = "cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36"},
{file = "cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e"},
{file = "cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b"},
{file = "cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001"},
{file = "cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b"},
{file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838"},
{file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5"},
{file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615"},
{file = "cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6"},
{file = "cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6"},
{file = "cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493"},
]
[package.dependencies]
cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""}
[package.extras]
ssh = ["bcrypt (>=3.1.5)"]
[[package]]
name = "debugpy"
version = "1.8.17"
@@ -1270,12 +1208,12 @@ version = "2.23"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
groups = ["dev"]
markers = "implementation_name == \"pypy\""
files = [
{file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"},
{file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"},
]
markers = {main = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", dev = "implementation_name == \"pypy\""}
[[package]]
name = "pydantic"
@@ -1449,24 +1387,6 @@ files = [
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyjwt"
version = "2.13.0"
description = "JSON Web Token implementation in Python"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728"},
{file = "pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423"},
]
[package.dependencies]
cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""}
[package.extras]
crypto = ["cryptography (>=3.4.0)"]
[[package]]
name = "pypdf"
version = "6.2.0"
@@ -2519,4 +2439,4 @@ files = [
[metadata]
lock-version = "2.1"
python-versions = ">=3.13"
content-hash = "a6a7fe19bb80521dd23f263f45280b9b9d8086960388456e98ca633569f98108"
content-hash = "26d850ac6adbc2394225e7db95e36b97cf87c43bb0e390630f911f30d1e5e1fc"
+1 -2
View File
@@ -1,6 +1,6 @@
[project]
name = "myice"
version = "v0.6.1"
version = "v0.6.0"
description = "myice parsing"
authors = [
{ name = "Rene Luria", "email" = "<rene@luria.ch>"},
@@ -15,7 +15,6 @@ dependencies = [
"pypdf (>=6.0.0)",
"rl-ai-tools >=1.9.0",
"fastapi[standard] (>=0.115.11)",
"pyjwt[crypto] (>=2.10.1)",
]
[tool.poetry.dependencies]