diff --git a/.gitignore b/.gitignore index 3122c83..457af01 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .vscode main.py frt_prueba.txt +.idea # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 622c5c7..7de00e2 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,14 @@ Frt00000 Frt00001 ``` +También se puede consultar por serial de medidor usando la opción `--meter-serial`: + +```powershell +> enerbitdso usages fetch --meter-serial "S4AM22060202018" --since 20230401 --until 20230405 +``` + +> **Nota:** `--meter-serial` es mutuamente excluyente con los códigos de frontera (`--frt-file` o argumentos FRTS). + #### Especificación de intervalo de tiempo para la consulta El intervalo de tiempo se define a través de los parámetros de tipo fecha `--since` y `--until` (desde y hasta, respectivamente). @@ -140,150 +148,168 @@ También tiene opción `--help` que muestra la ayuda particular de este sub-coma │ --frt-file PATH Path file with one frt code per line [default: None] │ │ --connection_timeout INTEGER RANGE The timeout used for HTTP connection in seconds[0<=x<=20][default: 10]│ │ --read_timeout INTEGER RANGE The timeout used for HTTP requests in seconds[60<=x<=120][default: 60]│ +│ --meter-serial TEXT Serial del medidor a consultar [default: None] │ │ --help Show this message and exit. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ``` # Librería DSO -Para poder hacer uso de la librería DSO se debe hacer lo siguiente - -## Inicializar el constructor +Para uso programático, `DSOClient` es un cliente asíncrono que debe usarse con `async with` y `await`. -Para ello se debe importar el constructor de la siguiente forma: +## Uso básico ```python +import asyncio +import datetime as dt from enerbitdso.enerbit import DSOClient -``` - -La inicialización se debe hacer asi: -```python -ebconnector = DSOClient( - api_base_url="https://dso.enerbit.me/", - api_username="usuario_del_DSO", - api_password="contraseña_del_DSO", -) -``` +since = dt.datetime(2023, 4, 1, tzinfo=dt.timezone.utc) +until = dt.datetime(2023, 4, 5, tzinfo=dt.timezone.utc) -Al tener el objeto ya se pueden realizar consultas de la siguiente forma: +async def main(): + async with DSOClient( + api_base_url="https://dso.enerbit.me", + api_username="usuario@empresa.com", + api_password="contraseña", + ) as client: + records = await client.fetch_schedule_usage_records_large_interval( + frt_code="Frt00000", since=since, until=until + ) -```python -usage_records = ebconnector.fetch_schedule_usage_records_large_interval( - frt_code=frt_code, since=since, until=until -) +asyncio.run(main()) ``` -Tambien se puede hacer una consulta de perfiles de la siguiente forma: +También se puede consultar por serial de medidor: ```python -schedule_records = ebconnector.fetch_schedule_measurements_records_large_interval( - frt_code=frt_code, since=since, until=until -) +async def main(): + async with DSOClient( + api_base_url="https://dso.enerbit.me", + api_username="usuario@empresa.com", + api_password="contraseña", + ) as client: + records = await client.fetch_schedule_usage_records_large_interval( + meter_serial="S4AM22060202018", since=since, until=until + ) ``` -## Configuración del Cliente DSO - -### Parámetros Básicos +Y para consultar perfiles de medición: ```python -ebconnector = DSOClient( - api_base_url="https://dso.enerbit.me/", - api_username="tu_usuario@empresa.com", - api_password="tu_contraseña" -) +async def main(): + async with DSOClient( + api_base_url="https://dso.enerbit.me", + api_username="usuario@empresa.com", + api_password="contraseña", + ) as client: + schedule_records = await client.fetch_schedule_measurements_records_large_interval( + frt_code="Frt00000", since=since, until=until + ) ``` -### Configuración Avanzada con Timeouts +## Parámetros de timeout -Para mejorar la estabilidad en consultas masivas, especialmente cuando se procesan muchas fronteras, se recomienda configurar timeouts personalizados: +Para mejorar la estabilidad en consultas masivas se pueden configurar timeouts personalizados: ```python -ebconnector = DSOClient( - api_base_url="https://dso.enerbit.me/", +async with DSOClient( + api_base_url="https://dso.enerbit.me", api_username="tu_usuario@empresa.com", api_password="tu_contraseña", - connection_timeout=20, # Timeout de conexión en segundos (1-60) - read_timeout=120 # Timeout de lectura en segundos (60-300) -) + connection_timeout=20, # Timeout de conexión en segundos (0-20, default: 10) + read_timeout=120 # Timeout de lectura en segundos (60-120, default: 60) +) as client: + ... ``` -### Parámetros de Timeout - -- **connection_timeout**: Tiempo máximo para establecer conexión con el servidor (recomendado: 10-30 segundos) -- **read_timeout**: Tiempo máximo para recibir respuesta del servidor (recomendado: 60-180 segundos) - -### Configuración con Variables de Entorno +## Variables de entorno para credenciales Una práctica recomendada es usar variables de entorno para las credenciales: ```python +import asyncio import os +from enerbitdso.enerbit import DSOClient -ebconnector = DSOClient( - api_base_url=os.getenv("DSO_HOST", "https://dso.enerbit.me/"), - api_username=os.getenv("DSO_USERNAME"), - api_password=os.getenv("DSO_PASSWORD"), - connection_timeout=20, - read_timeout=120 -) +async def main(): + async with DSOClient( + api_base_url=os.getenv("DSO_HOST", "https://dso.enerbit.me"), + api_username=os.getenv("DSO_USERNAME"), + api_password=os.getenv("DSO_PASSWORD"), + connection_timeout=20, + read_timeout=120, + ) as client: + ... + +asyncio.run(main()) ``` -Configurar las variables de entorno: - **Linux/macOS:** ```bash -export DSO_HOST="https://dso.enerbit.me/" +export DSO_HOST="https://dso.enerbit.me" export DSO_USERNAME="tu_usuario@empresa.com" export DSO_PASSWORD="tu_contraseña" ``` **Windows:** ```cmd -set DSO_HOST=https://dso.enerbit.me/ +set DSO_HOST=https://dso.enerbit.me set DSO_USERNAME=tu_usuario@empresa.com set DSO_PASSWORD=tu_contraseña ``` -# Ejemplo de Uso Masivo +# Ejemplos + +El repositorio incluye dos archivos de ejemplo para distintos casos de uso. -## Archivo `example.py` +## `basic_example.py` — Procesamiento secuencial -El repositorio incluye un archivo `example.py` que demuestra cómo procesar múltiples fronteras de manera eficiente usando concurrencia. Este ejemplo es útil para: +Para uso simple o pocas fronteras. Procesa cada frontera una por una, lo que lo hace fácil de entender y depurar. + +- Sin concurrencia +- Sin reintentos automáticos +- Muestra el progreso cada 500 fronteras +- Genera un archivo Excel con matrices horarias de consumo + +### Uso: + +1. Configurar variables de entorno (como se describe arriba) o editar las constantes `DSO_USER`, `DSO_PASSWORD` y `DSO_BASE_URL` en el script. +2. Crear archivo de fronteras `frt_prueba.txt` con una frontera por línea. +3. Ejecutar: + ```bash + python basic_example.py + ``` -- **Procesamiento masivo de fronteras**: Consulta múltiples fronteras en paralelo -- **Manejo de errores**: Implementa reintentos automáticos y reportes de fronteras fallidas -- **Generación de reportes**: Crea archivos Excel con matrices horarias de consumo -- **Monitoreo de progreso**: Muestra el avance del procesamiento cada 500 fronteras +## `concurrent_example.py` — Procesamiento concurrente -### Características del ejemplo: +Para procesamiento masivo (cientos o miles de fronteras). Lanza todas las consultas en paralelo con un límite de concurrencia. -- 🚀 **Concurrencia**: Usa ThreadPoolExecutor para procesar múltiples fronteras simultáneamente -- 🔄 **Reintentos**: Implementa backoff exponencial para manejar errores de red -- 📊 **Progreso visual**: Muestra estadísticas de avance durante la ejecución -- 📈 **Salida estructurada**: Genera matrices Excel organizadas por hora, día, mes y año -- ⚠️ **Manejo de errores**: Reporta fronteras fallidas para análisis posterior +- Usa `asyncio.Semaphore(5)` para limitar la concurrencia máxima a 5 solicitudes simultáneas +- Reintentos con backoff exponencial (hasta 3 intentos por frontera) +- Monitoreo de progreso cada 500 fronteras +- Genera un archivo Excel con matrices horarias de consumo -### Uso del ejemplo: +### Uso: -1. **Configurar variables de entorno** (como se describe arriba) -2. **Crear archivo de fronteras**: `frt_prueba.txt` con una frontera por línea -3. **Ejecutar el script**: +1. Configurar variables de entorno o editar las constantes en el script. +2. Crear archivo de fronteras `frt_prueba.txt` con una frontera por línea. +3. Ejecutar: ```bash - python example.py + python concurrent_example.py ``` -### Archivos generados: +### Archivos generados por ambos ejemplos: -- `Matrices_YYYYMMDD_HHMM.xlsx` - Datos principales organizados por matrices horarias -- `fronteras_fallidas_YYYYMMDD_HHMM.txt` - Lista de fronteras que no se pudieron procesar +- `Matrices_YYYYMMDD_HHMM.xlsx` — Datos principales organizados por matrices horarias +- `fronteras_fallidas_YYYYMMDD_HHMM.txt` — Lista de fronteras que no se pudieron procesar ### Configuración recomendada para producción: Para ambientes de producción o consultas masivas, ajusta estos parámetros en el ejemplo: -- **max_workers**: Reduce de 30 a 5-10 para evitar saturar el servidor +- **semaphore**: Límite de conexiones concurrentes. Reduce de 30 a 5-10 para evitar saturar el servidor - **Timeouts**: Usa connection_timeout=20 y read_timeout=120 como mínimo - **Intervalos de tiempo**: Limita los rangos de fechas para consultas más eficientes -El archivo `example.py` sirve como base para desarrollar tus propios scripts de procesamiento masivo de datos de enerBit DSO. +Los archivos `*_example.py` sirve como base para desarrollar tus propios scripts de procesamiento masivo de datos de enerBit DSO. diff --git a/basic_example.py b/basic_example.py new file mode 100644 index 0000000..2e53752 --- /dev/null +++ b/basic_example.py @@ -0,0 +1,140 @@ +import asyncio +import os +import time +import traceback +from datetime import datetime as dt +from zoneinfo import ZoneInfo + +import pandas as pd + +from enerbitdso.enerbit import DSOClient + +colombia_tz = ZoneInfo("America/Bogota") + +since = dt.strptime("2026-02-01T00:00-05:00", "%Y-%m-%dT%H:%M%z") +until = dt.strptime("2026-02-08T00:00-05:00", "%Y-%m-%dT%H:%M%z") + +with open("frt_prueba.txt", "r") as f1: + frontiers = [line.strip() for line in f1 if line.strip()] + +usage_records_dict = [] +fronteras_fallidas = [] + +print("Generando archivo...") + + +async def main(): + start = time.perf_counter() + total_frontiers = len(frontiers) + + async with DSOClient( + api_base_url=os.getenv("DSO_HOST"), + api_username=os.getenv("DSO_USERNAME"), + api_password=os.getenv("DSO_PASSWORD"), + connection_timeout=20, + read_timeout=120, + ) as ebconnector: + for i, frontier in enumerate(frontiers, 1): + try: + usage_records = await ebconnector.fetch_schedule_usage_records_large_interval( + frt_code=frontier, since=since, until=until + ) + + if not usage_records: + print(f"[INFO] No se encontraron datos para la frontera {frontier}.") + else: + usage_records_dict.extend( + [ + { + "Frontera": usage_record.frt_code + if usage_record.frt_code is not None + else "SIN_FRONTERA", + "Serial": usage_record.meter_serial, + "time_start": str( + usage_record.time_start.astimezone(colombia_tz).strftime("%Y-%m-%d %H:%M:%S%z") + ), + "time_end": str( + usage_record.time_end.astimezone(colombia_tz).strftime("%Y-%m-%d %H:%M:%S%z") + ), + "kWhD": usage_record.active_energy_imported, + "kWhR": usage_record.active_energy_exported, + "kVarhD": usage_record.reactive_energy_imported, + "kVarhR": usage_record.reactive_energy_exported, + } + for usage_record in usage_records + ] + ) + + except Exception as e: + print(f"[ERROR] Error procesando la frontera {frontier}: {e}") + fronteras_fallidas.append(frontier) + traceback.print_exc() + + if i % 500 == 0 or i == total_frontiers: + print(f"📊 Progreso: {i}/{total_frontiers} fronteras procesadas ({i / total_frontiers * 100:.1f}%)") + + # Generar reporte de fronteras fallidas + if fronteras_fallidas: + timestamp_failed = dt.now().strftime("%Y%m%d_%H%M") + failed_filename = f"fronteras_fallidas_{timestamp_failed}.txt" + + with open(failed_filename, "w") as out: + out.write("\n".join(fronteras_fallidas)) + + print(f"\n❌ {len(fronteras_fallidas)} fronteras fallaron y se guardaron en: {failed_filename}") + print(f"Fronteras exitosas: {total_frontiers - len(fronteras_fallidas)}/{total_frontiers}") + else: + print(f"\n✅ Todas las {total_frontiers} fronteras se procesaron exitosamente.") + + if not usage_records_dict: + print("⚠️ No se encontraron registros para ninguna frontera. Terminando script.") + return + + print("\n🔄 Procesando datos y generando Excel...") + + df = pd.DataFrame(usage_records_dict) + df["time_start"] = pd.to_datetime(df["time_start"]) + + df["Año"] = df["time_start"].dt.year + df["Mes"] = df["time_start"].dt.month + df["Día"] = df["time_start"].dt.day + df["hora_en_punto"] = df["time_start"].dt.hour + + cuadrante = ["kWhD", "kWhR", "kVarhD", "kVarhR"] + df_long = df.melt( + id_vars=["Frontera", "Serial", "Año", "Mes", "Día", "hora_en_punto"], + value_vars=cuadrante, + var_name="Tipo", + value_name="valor_cuadrante", + ) + + horas = list(range(24)) + resultado = ( + df_long.pivot_table( + index=["Serial", "Frontera", "Tipo", "Año", "Mes", "Día"], + columns="hora_en_punto", + values="valor_cuadrante", + aggfunc="first", + ) + .reindex(columns=horas, fill_value=0) + .reset_index() + ) + resultado.columns.name = None + resultado = resultado.rename(columns={col: f"Hora {col}" for col in resultado.columns if isinstance(col, int)}) + + timestamp = dt.now().strftime("%Y%m%d_%H%M") + filename = f"Matrices_{timestamp}.xlsx" + resultado.to_excel(filename, index=False) + + print(f"\n✅ Archivo generado correctamente: {filename}") + + # Resumen final + print("\n📋 RESUMEN FINAL:") + print(f" • Total fronteras: {total_frontiers}") + print(f" • Exitosas: {total_frontiers - len(fronteras_fallidas)}") + print(f" • Fallidas: {len(fronteras_fallidas)}") + print(f" • Registros procesados: {len(usage_records_dict)}") + print(time.perf_counter() - start) + + +asyncio.run(main()) diff --git a/concurrent_example.py b/concurrent_example.py new file mode 100644 index 0000000..96b9e38 --- /dev/null +++ b/concurrent_example.py @@ -0,0 +1,158 @@ +import asyncio +import os +import random +import time +import traceback +from datetime import datetime as dt +from zoneinfo import ZoneInfo + +import pandas as pd + +from enerbitdso.enerbit import DSOClient + +colombia_tz = ZoneInfo("America/Bogota") + +since = dt.strptime("2026-02-01T00:00-05:00", "%Y-%m-%dT%H:%M%z") +until = dt.strptime("2026-02-08T00:00-05:00", "%Y-%m-%dT%H:%M%z") + +with open("frt_prueba.txt", "r") as f1: + frontiers = [line.strip() for line in f1 if line.strip()] + +usage_records_dict: list[dict] = [] +fronteras_fallidas: list[str] = [] + +print("Generando archivo...") + + +async def fetch_usage_records(ebconnector, frontier, semaphore, fronteras_fallidas, max_retries=3): + for attempt in range(max_retries): + try: + async with semaphore: + usage_records = await ebconnector.fetch_schedule_usage_records_large_interval( + frt_code=frontier, since=since, until=until + ) + + if not usage_records: + print(f"[INFO] No se encontraron datos para la frontera {frontier}.") + return [] + + return [ + { + "Frontera": usage_record.frt_code if usage_record.frt_code is not None else "SIN_FRONTERA", + "Serial": usage_record.meter_serial, + "time_start": str(usage_record.time_start.astimezone(colombia_tz).strftime("%Y-%m-%d %H:%M:%S%z")), + "time_end": str(usage_record.time_end.astimezone(colombia_tz).strftime("%Y-%m-%d %H:%M:%S%z")), + "kWhD": usage_record.active_energy_imported, + "kWhR": usage_record.active_energy_exported, + "kVarhD": usage_record.reactive_energy_imported, + "kVarhR": usage_record.reactive_energy_exported, + } + for usage_record in usage_records + ] + + except Exception as e: + if attempt < max_retries - 1: + wait_time = (2**attempt) + random.uniform(0, 1) + print( + f"[RETRY] Frontera {frontier}, intento {attempt + 1}/{max_retries}." + f" Esperando {wait_time:.1f}s...\n{traceback.format_exc()}" + ) + await asyncio.sleep(wait_time) + continue + else: + print(f"[ERROR] Error procesando la frontera {frontier} después de {max_retries} intentos: {e}") + fronteras_fallidas.append(frontier) + return [] + + +async def main(): + start = time.perf_counter() + semaphore = asyncio.Semaphore(30) + total_frontiers = len(frontiers) + processed_count = 0 + + async with DSOClient( + api_base_url=os.getenv("DSO_HOST"), + api_username=os.getenv("DSO_USERNAME"), + api_password=os.getenv("DSO_PASSWORD"), + connection_timeout=20, + read_timeout=120, + ) as ebconnector: + tasks = [ + asyncio.create_task(fetch_usage_records(ebconnector, f, semaphore, fronteras_fallidas)) for f in frontiers + ] + + for coro in asyncio.as_completed(tasks): + records = await coro + usage_records_dict.extend(records) + processed_count += 1 + + if processed_count % 500 == 0 or processed_count == total_frontiers: + pct = processed_count / total_frontiers * 100 + print(f"📊 Progreso: {processed_count}/{total_frontiers} fronteras procesadas ({pct:.1f}%)") + + # Generar reporte de fronteras fallidas + if fronteras_fallidas: + timestamp_failed = dt.now().strftime("%Y%m%d_%H%M") + failed_filename = f"fronteras_fallidas_{timestamp_failed}.txt" + + with open(failed_filename, "w") as out: + out.write("\n".join(fronteras_fallidas)) + + print(f"\n❌ {len(fronteras_fallidas)} fronteras fallaron y se guardaron en: {failed_filename}") + print(f"Fronteras exitosas: {total_frontiers - len(fronteras_fallidas)}/{total_frontiers}") + else: + print(f"\n✅ Todas las {total_frontiers} fronteras se procesaron exitosamente.") + + if not usage_records_dict: + print("⚠️ No se encontraron registros para ninguna frontera. Terminando script.") + return + + print("\n🔄 Procesando datos y generando Excel...") + + df = pd.DataFrame(usage_records_dict) + df["time_start"] = pd.to_datetime(df["time_start"]) + + df["Año"] = df["time_start"].dt.year + df["Mes"] = df["time_start"].dt.month + df["Día"] = df["time_start"].dt.day + df["hora_en_punto"] = df["time_start"].dt.hour + + cuadrante = ["kWhD", "kWhR", "kVarhD", "kVarhR"] + df_long = df.melt( + id_vars=["Frontera", "Serial", "Año", "Mes", "Día", "hora_en_punto"], + value_vars=cuadrante, + var_name="Tipo", + value_name="valor_cuadrante", + ) + + horas = list(range(24)) + resultado = ( + df_long.pivot_table( + index=["Serial", "Frontera", "Tipo", "Año", "Mes", "Día"], + columns="hora_en_punto", + values="valor_cuadrante", + aggfunc="first", + ) + .reindex(columns=horas, fill_value=0) + .reset_index() + ) + resultado.columns.name = None + resultado = resultado.rename(columns={col: f"Hora {col}" for col in resultado.columns if isinstance(col, int)}) + + timestamp = dt.now().strftime("%Y%m%d_%H%M") + filename = f"Matrices_{timestamp}.xlsx" + resultado.to_excel(filename, index=False) + + print(f"\n✅ Archivo generado correctamente: {filename}") + + # Resumen final + print("\n📋 RESUMEN FINAL:") + print(f" • Total fronteras: {total_frontiers}") + print(f" • Exitosas: {total_frontiers - len(fronteras_fallidas)}") + print(f" • Fallidas: {len(fronteras_fallidas)}") + print(f" • Registros procesados: {len(usage_records_dict)}") + print(time.perf_counter() - start) + + +asyncio.run(main()) diff --git a/example.py b/example.py deleted file mode 100644 index 3b5688e..0000000 --- a/example.py +++ /dev/null @@ -1,138 +0,0 @@ -from enerbitdso.enerbit import DSOClient -from datetime import datetime as dt -import pandas as pd -import pytz -import os -from concurrent.futures import ThreadPoolExecutor, as_completed -import time -import random - -colombia_tz = pytz.timezone('America/Bogota') - -ebconnector = DSOClient( - api_base_url=os.getenv("DSO_HOST"), - api_username=os.getenv("DSO_USERNAME"), - api_password=os.getenv("DSO_PASSWORD"), - connection_timeout=20, - read_timeout=120 -) -since = dt.strptime("2025-09-04T00:00-05:00", "%Y-%m-%dT%H:%M%z") -until = dt.strptime("2025-09-08T00:00-05:00", "%Y-%m-%dT%H:%M%z") - -with open("frt_prueba.txt", "r") as f1: - frontiers = [line.strip() for line in f1 if line.strip()] - -usage_records_dict = [] -fronteras_fallidas = [] - -print("Generando archivo...") - -def fetch_usage_records(frontier, max_retries=3): - for attempt in range(max_retries): - try: - usage_records = ebconnector.fetch_schedule_usage_records_large_interval( - frt_code=frontier, since=since, until=until - ) - - if not usage_records: - print(f"[INFO] No se encontraron datos para la frontera {frontier}.") - return [] - - return [{ - "Frontera": usage_record.frt_code if usage_record.frt_code is not None else "SIN_FRONTERA", - "Serial": usage_record.meter_serial, - "time_start": str(usage_record.time_start.astimezone(colombia_tz).strftime('%Y-%m-%d %H:%M:%S%z')), - "time_end": str(usage_record.time_end.astimezone(colombia_tz).strftime('%Y-%m-%d %H:%M:%S%z')), - "kWhD": usage_record.active_energy_imported, - "kWhR": usage_record.active_energy_exported, - "kVarhD": usage_record.reactive_energy_imported, - "kVarhR": usage_record.reactive_energy_exported - } for usage_record in usage_records] - - except Exception as e: - if attempt < max_retries - 1: - # Backoff exponencial con jitter - wait_time = (2 ** attempt) + random.uniform(0, 1) - print(f"[RETRY] Frontera {frontier}, intento {attempt + 1}/{max_retries}. Esperando {wait_time:.1f}s...") - time.sleep(wait_time) - continue - else: - print(f"[ERROR] Error procesando la frontera {frontier} después de {max_retries} intentos: {e}") - fronteras_fallidas.append(frontier) - return [] - -with ThreadPoolExecutor(max_workers=30) as executor: - future_to_frontier = {executor.submit(fetch_usage_records, frontier): frontier for frontier in frontiers} - - processed_count = 0 - total_frontiers = len(frontiers) - - for future in as_completed(future_to_frontier): - usage_records_dict.extend(future.result()) - processed_count += 1 - - # Mostrar progreso cada 500 fronteras o al final - if processed_count % 500 == 0 or processed_count == total_frontiers: - print(f"📊 Progreso: {processed_count}/{total_frontiers} fronteras procesadas ({processed_count/total_frontiers*100:.1f}%)") - -# Generar reporte de fronteras fallidas -if fronteras_fallidas: - timestamp_failed = dt.now().strftime("%Y%m%d_%H%M") - failed_filename = f"fronteras_fallidas_{timestamp_failed}.txt" - - with open(failed_filename, "w") as out: - out.write("\n".join(fronteras_fallidas)) - - print(f"\n❌ {len(fronteras_fallidas)} fronteras fallaron y se guardaron en: {failed_filename}") - print(f"Fronteras exitosas: {total_frontiers - len(fronteras_fallidas)}/{total_frontiers}") -else: - print(f"\n✅ Todas las {total_frontiers} fronteras se procesaron exitosamente.") - -if not usage_records_dict: - print("⚠️ No se encontraron registros para ninguna frontera. Terminando script.") - exit() - -print("\n🔄 Procesando datos y generando Excel...") - -df = pd.DataFrame(usage_records_dict) -df['time_start'] = pd.to_datetime(df['time_start']) - -df['Año'] = df['time_start'].dt.year -df['Mes'] = df['time_start'].dt.month -df['Día'] = df['time_start'].dt.day -df['hora_en_punto'] = df['time_start'].dt.hour - -cuadrante = ["kWhD", "kWhR", "kVarhD", "kVarhR"] -df_long = df.melt( - id_vars=["Frontera", "Serial", "Año", "Mes", "Día", "hora_en_punto"], - value_vars=cuadrante, - var_name="Tipo", - value_name="valor_cuadrante" -) - -horas = list(range(24)) -resultado = ( - df_long.pivot_table( - index=["Serial", "Frontera", "Tipo", "Año", "Mes", "Día"], - columns="hora_en_punto", - values="valor_cuadrante", - aggfunc="first" - ) - .reindex(columns=horas, fill_value=0) - .reset_index() -) -resultado.columns.name = None -resultado = resultado.rename(columns={col: f"Hora {col}" for col in resultado.columns if isinstance(col, int)}) - -timestamp = dt.now().strftime("%Y%m%d_%H%M") -filename = f"Matrices_{timestamp}.xlsx" -resultado.to_excel(filename, index=False) - -print(f"\n✅ Archivo generado correctamente: {filename}") - -# Resumen final -print(f"\n📋 RESUMEN FINAL:") -print(f" • Total fronteras: {total_frontiers}") -print(f" • Exitosas: {total_frontiers - len(fronteras_fallidas)}") -print(f" • Fallidas: {len(fronteras_fallidas)}") -print(f" • Registros procesados: {len(usage_records_dict)}") diff --git a/pyproject.toml b/pyproject.toml index d24d055..1652621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,12 +65,9 @@ addopts = ["--import-mode=importlib"] [tool.ruff] line-length = 119 -max-complexity = 15 -indent-width = 4 [tool.ruff.lint] select = ["B", "C", "E", "F", "W", "B9", "I"] -ignore = ["E203", "E501", "W503"] [tool.ruff.lint.flake8-bugbear] extend-immutable-calls = [ diff --git a/src/enerbitdso/VERSION b/src/enerbitdso/VERSION index 964f548..6cd3286 100644 --- a/src/enerbitdso/VERSION +++ b/src/enerbitdso/VERSION @@ -1 +1 @@ -0.1.20 \ No newline at end of file +0.1.21 \ No newline at end of file diff --git a/src/enerbitdso/__init__.py b/src/enerbitdso/__init__.py index 27939fd..2ba799d 100644 --- a/src/enerbitdso/__init__.py +++ b/src/enerbitdso/__init__.py @@ -1 +1,2 @@ -from .enerbit import DSOClient, InvalidParameterError \ No newline at end of file +from .enerbit import DSOClient as DSOClient +from .enerbit import InvalidParameterError as InvalidParameterError diff --git a/src/enerbitdso/cli.py b/src/enerbitdso/cli.py index a7ca7c4..84ee0da 100644 --- a/src/enerbitdso/cli.py +++ b/src/enerbitdso/cli.py @@ -1,14 +1,15 @@ +import asyncio import datetime as dt import enum import logging import operator import pathlib import sys +import zoneinfo from typing import Annotated, TypedDict import pydantic import typer -import zoneinfo from rich.console import Console from enerbitdso import enerbit, formats @@ -57,7 +58,7 @@ def today(): @usages.command() -def fetch( +def fetch( # noqa: C901 api_base_url: Annotated[str, typer.Option(..., envvar="ENERBIT_API_BASE_URL")], api_username: Annotated[str, typer.Option(..., envvar="ENERBIT_API_USERNAME")], api_password: Annotated[ @@ -74,12 +75,8 @@ def fetch( formats=DATE_FORMATS, show_default="today", ), - out_format: OutputFormat = typer.Option( - "jsonl", help="Output file format", case_sensitive=False - ), - frt_file: pathlib.Path = typer.Option( - None, help="Path file with one frt code per line" - ), + out_format: OutputFormat = typer.Option("jsonl", help="Output file format", case_sensitive=False), + frt_file: pathlib.Path = typer.Option(None, help="Path file with one frt code per line"), connection_timeout: int = typer.Option( 10, min=0, @@ -92,9 +89,7 @@ def fetch( max=20, help="Config the timeout for HTTP requests (in seconds)", ), - meter_serial: str = typer.Option( - None, help="Filter by specific meter serial number" - ), + meter_serial: str = typer.Option(None, help="Filter by specific meter serial number"), frts: list[str] = typer.Argument(None, help="List of frt codes separated by ' '"), ): ebconnector = enerbit.DSOClient( @@ -121,7 +116,7 @@ def fetch( if not operator.xor(frt_file is not None, len(frts) != 0) and meter_serial is None: err_console.print("Debe proporcionar FRTs (--frt-file o argumentos FRTS) o --meter-serial, pero no ambos") raise typer.Exit(code=1) - + if meter_serial and (frt_file is not None or len(frts) != 0): err_console.print("No se puede usar --meter-serial junto con FRTs. Use uno u otro.") raise typer.Exit(code=1) @@ -131,48 +126,47 @@ def fetch( frts = frts_src.read().splitlines() if meter_serial: - err_console.print( - f"Fetching usages for meter {meter_serial} since={since} until={until}" - ) + err_console.print(f"Fetching usages for meter {meter_serial} since={since} until={until}") query_type = "meter" items_to_process = [meter_serial] else: - err_console.print( - f"Fetching usages for {len(frts)} frts since={since} until={until}" - ) + err_console.print(f"Fetching usages for {len(frts)} frts since={since} until={until}") query_type = "frt" items_to_process = frts - with ebconnector: + async def _run(): header = True - for i, item in enumerate(items_to_process, 1): - try: - if query_type == "meter": - usage_records = ebconnector.fetch_schedule_usage_records_large_interval( - frt_code=None, since=since, until=until, meter_serial=item - ) - item_description = f"meter {item}" - else: - usage_records = ebconnector.fetch_schedule_usage_records_large_interval( - frt_code=item, since=since, until=until, meter_serial=None - ) - item_description = f"frt code {item}" - - except Exception: - err_console.print(f"Failed to fetch usage records for {item_description}") - err_console.print_exception() - continue - - match out_format: - case OutputFormat.csv: - content = formats.as_csv(usage_records, header=header) - header = False - case OutputFormat.jsonl: - content = formats.as_jsonl(usage_records) - - content.seek(0) - for s in content: - sys.stdout.write(s) + async with ebconnector: + for _i, item in enumerate(items_to_process, 1): + try: + if query_type == "meter": + usage_records = await ebconnector.fetch_schedule_usage_records_large_interval( + frt_code=None, since=since, until=until, meter_serial=item + ) + item_description = f"meter {item}" + else: + usage_records = await ebconnector.fetch_schedule_usage_records_large_interval( + frt_code=item, since=since, until=until, meter_serial=None + ) + item_description = f"frt code {item}" + + except Exception: + err_console.print(f"Failed to fetch usage records for {item_description}") + err_console.print_exception() + continue + + match out_format: + case OutputFormat.csv: + content = formats.as_csv(usage_records, header=header) + header = False + case OutputFormat.jsonl: + content = formats.as_jsonl(usage_records) + + content.seek(0) + for s in content: + sys.stdout.write(s) + + asyncio.run(_run()) if __name__ == "__main__": diff --git a/src/enerbitdso/config.py b/src/enerbitdso/config.py index 3d559ff..7b6457f 100644 --- a/src/enerbitdso/config.py +++ b/src/enerbitdso/config.py @@ -2,12 +2,8 @@ class Config(pydantic_settings.BaseSettings): - DAY_FILENAME_TEMPLATE: str = ( - "STAR_{operation_day:%Y%m%d}_{serial}_Day_{now:%Y%m%d%H%M%S}.xml" - ) - INTERVAL_FILENAME_TEMPLATE: str = ( - "STAR_{operation_day:%Y%m%d}_{serial}_Interval_{now:%Y%m%d%H%M%S}.xml" - ) + DAY_FILENAME_TEMPLATE: str = "STAR_{operation_day:%Y%m%d}_{serial}_Day_{now:%Y%m%d%H%M%S}.xml" + INTERVAL_FILENAME_TEMPLATE: str = "STAR_{operation_day:%Y%m%d}_{serial}_Interval_{now:%Y%m%d%H%M%S}.xml" settings = Config() diff --git a/src/enerbitdso/enerbit.py b/src/enerbitdso/enerbit.py index 11f3036..5616a3b 100644 --- a/src/enerbitdso/enerbit.py +++ b/src/enerbitdso/enerbit.py @@ -1,11 +1,12 @@ +import asyncio +import base64 import datetime as dt +import json import logging import math import ssl import urllib import urllib.parse -import base64 -import json from typing import Optional import httpx @@ -24,46 +25,46 @@ class InvalidParameterError(ValueError): """Error cuando los parámetros de consulta son inválidos""" + pass def _validate_query_parameters(frt_code: Optional[str], meter_serial: Optional[str]) -> None: """Valida que exactamente uno de frt_code o meter_serial esté presente. - + Args: frt_code: Código de frontera (opcional) meter_serial: Número de serie del medidor (opcional) - + Raises: InvalidParameterError: Si ambos parámetros están presentes o si ambos están ausentes """ has_frt = frt_code is not None and frt_code.strip() != "" has_meter = meter_serial is not None and meter_serial.strip() != "" - + if has_frt and has_meter: raise InvalidParameterError( "No se pueden especificar tanto 'frt_code' como 'meter_serial' al mismo tiempo. " "Debe proporcionar exactamente uno de los dos parámetros." ) - + if not has_frt and not has_meter: raise InvalidParameterError( - "Debe especificar al menos uno de 'frt_code' o 'meter_serial'. " - "No se pueden omitir ambos parámetros." + "Debe especificar al menos uno de 'frt_code' o 'meter_serial'. No se pueden omitir ambos parámetros." ) def _decode_jwt_payload(token: str) -> dict: """Decodifica el payload de un JWT sin verificar la firma""" try: - parts = token.split('.') + parts = token.split(".") if len(parts) != 3: return {} - + payload = parts[1] - payload += '=' * (4 - len(payload) % 4) + payload += "=" * (4 - len(payload) % 4) decoded_bytes = base64.urlsafe_b64decode(payload) - return json.loads(decoded_bytes.decode('utf-8')) + return json.loads(decoded_bytes.decode("utf-8")) except Exception: return {} @@ -71,8 +72,8 @@ def _decode_jwt_payload(token: str) -> dict: def _get_token_expiration(token: str) -> Optional[dt.datetime]: """Extrae la fecha de expiración de un JWT""" payload = _decode_jwt_payload(token) - if 'exp' in payload: - return dt.datetime.fromtimestamp(payload['exp']) + if "exp" in payload: + return dt.datetime.fromtimestamp(payload["exp"]) return None @@ -104,92 +105,70 @@ class ScheduleMeasurementRecord(pydantic.BaseModel): reactive_energy_exported: Optional[float] = None -def get_auth_token(base_url: str, username: str, password: pydantic.SecretStr) -> dict: +async def get_auth_token(base_url: str, username: str, password: pydantic.SecretStr) -> dict: """Obtiene tokens de autenticación (access y refresh)""" path = "/auth/token/" data = {"username": username, "password": password.get_secret_value()} - with httpx.Client(base_url=base_url, timeout=TIMEOUT, verify=SSL_CONTEXT) as client: - response = client.post(path, data=data) + async with httpx.AsyncClient(base_url=base_url, timeout=TIMEOUT, verify=SSL_CONTEXT) as client: + response = await client.post(path, data=data) try: response.raise_for_status() except httpx.HTTPStatusError as e: logger.error(f"Failed to authenticate: {e}") logger.error(f"Response: {response.text}") raise - + token_data = response.json() return { "access_token": token_data["access_token"], "refresh_token": token_data.get("refresh_token"), - "token_type": token_data.get("token_type", "bearer") + "token_type": token_data.get("token_type", "bearer"), } -def get_client( - base_url: str, username: str, password: pydantic.SecretStr -) -> httpx.Client: +async def get_client(base_url: str, username: str, password: pydantic.SecretStr) -> httpx.AsyncClient: url_parse: urllib.parse.ParseResult = urllib.parse.urlparse(base_url) url = url_parse.geturl() - token_data = get_auth_token(url, username, password) + token_data = await get_auth_token(url, username, password) auth = {"Authorization": f"Bearer {token_data['access_token']}"} - return httpx.Client(base_url=url, headers=auth, timeout=TIMEOUT, verify=SSL_CONTEXT) + return httpx.AsyncClient(base_url=url, headers=auth, timeout=TIMEOUT, verify=SSL_CONTEXT) def scale_measurement_records(records: list[ScheduleMeasurementRecord], scale: float): for r in records: - r.active_energy_imported = ( - r.active_energy_imported * scale - if r.active_energy_imported is not None - else None - ) - r.active_energy_exported = ( - r.active_energy_exported * scale - if r.active_energy_exported is not None - else None - ) + r.active_energy_imported = r.active_energy_imported * scale if r.active_energy_imported is not None else None + r.active_energy_exported = r.active_energy_exported * scale if r.active_energy_exported is not None else None r.reactive_energy_imported = ( - r.reactive_energy_imported * scale - if r.reactive_energy_imported is not None - else None + r.reactive_energy_imported * scale if r.reactive_energy_imported is not None else None ) r.reactive_energy_exported = ( - r.reactive_energy_exported * scale - if r.reactive_energy_exported is not None - else None + r.reactive_energy_exported * scale if r.reactive_energy_exported is not None else None ) return records def scale_usage_records(records: list[ScheduleUsageRecord], scale: float): for r in records: - r.active_energy_imported = ( - r.active_energy_imported * scale - if r.active_energy_imported is not None - else None - ) - r.active_energy_exported = ( - r.active_energy_exported * scale - if r.active_energy_exported is not None - else None - ) + r.active_energy_imported = r.active_energy_imported * scale if r.active_energy_imported is not None else None + r.active_energy_exported = r.active_energy_exported * scale if r.active_energy_exported is not None else None r.reactive_energy_imported = ( - r.reactive_energy_imported * scale - if r.reactive_energy_imported is not None - else None + r.reactive_energy_imported * scale if r.reactive_energy_imported is not None else None ) r.reactive_energy_exported = ( - r.reactive_energy_exported * scale - if r.reactive_energy_exported is not None - else None + r.reactive_energy_exported * scale if r.reactive_energy_exported is not None else None ) return records -def get_schedule_usage_records( - client: httpx.Client, since: dt.datetime, until: dt.datetime, meter_serial: Optional[str] = None, frt_code: Optional[str] = None +async def get_schedule_usage_records( + client: httpx.AsyncClient, + since: dt.datetime, + until: dt.datetime, + meter_serial: Optional[str] = None, + frt_code: Optional[str] = None, ) -> list[ScheduleUsageRecord]: _validate_query_parameters(frt_code, meter_serial) - + path = "/measurements/schedules/usages" params = { "since": since.isoformat(), @@ -197,13 +176,13 @@ def get_schedule_usage_records( "period-string": "hour", "period-number": "1", } - + if frt_code and frt_code.strip(): params["frt-code"] = frt_code if meter_serial and meter_serial.strip(): params["meter-serial"] = meter_serial - - response = client.get( + + response = await client.get( path, params=params, ) @@ -220,23 +199,27 @@ def get_schedule_usage_records( return usage_records -def get_schedule_measurement_records( - client: httpx.Client, since: dt.datetime, until: dt.datetime, meter_serial: Optional[str] = None, frt_code: Optional[str] = None +async def get_schedule_measurement_records( + client: httpx.AsyncClient, + since: dt.datetime, + until: dt.datetime, + meter_serial: Optional[str] = None, + frt_code: Optional[str] = None, ) -> list[ScheduleMeasurementRecord]: _validate_query_parameters(frt_code, meter_serial) - + path = "/measurements/schedules/" params = { "since": since.isoformat(), "until": until.isoformat(), } - + if frt_code and frt_code.strip(): params["frt-code"] = frt_code if meter_serial and meter_serial.strip(): params["meter-serial"] = meter_serial - - response = client.get(path, params=params) + + response = await client.get(path, params=params) try: response.raise_for_status() except httpx.HTTPStatusError as e: @@ -246,18 +229,24 @@ def get_schedule_measurement_records( records = response.json() records = sorted(records, key=lambda r: r["time_local_utc"]) measurement_records = [ScheduleMeasurementRecord.model_validate(r) for r in records] - measurement_records = scale_measurement_records( - measurement_records, scale=WATT_HOUR_TO_KILOWATT_HOUR - ) + measurement_records = scale_measurement_records(measurement_records, scale=WATT_HOUR_TO_KILOWATT_HOUR) return measurement_records class DSOClient: - def __init__(self, api_username: str, api_password: str, api_base_url: str, connection_timeout: Optional[int] = None, read_timeout: Optional[int] = None) -> None: + def __init__( + self, + api_username: str, + api_password: str, + api_base_url: str, + connection_timeout: Optional[int] = None, + read_timeout: Optional[int] = None, + ) -> None: self.api_base_url = api_base_url self.api_username = api_username self.api_password = pydantic.SecretStr(api_password) - self._client: Optional[httpx.Client] = None + self._client: Optional[httpx.AsyncClient] = None + self._lock = asyncio.Lock() self._access_token: Optional[str] = None self._refresh_token: Optional[str] = None self._token_expires_at: Optional[dt.datetime] = None @@ -280,52 +269,56 @@ def _is_token_valid(self) -> bool: buffer = dt.timedelta(seconds=30) return dt.datetime.now() < (self._token_expires_at - buffer) - def _refresh_access_token(self) -> bool: + async def _refresh_access_token(self) -> bool: """Intenta refrescar el access token usando el refresh token""" if not self._refresh_token: return False - + path = "/auth/refresh/" data = {"refresh_token": self._refresh_token} - + try: - with httpx.Client(base_url=self.api_base_url, timeout=TIMEOUT, verify=SSL_CONTEXT) as client: - response = client.post(path, data=data) + async with httpx.AsyncClient(base_url=self.api_base_url, timeout=TIMEOUT, verify=SSL_CONTEXT) as client: + response = await client.post(path, json=data) response.raise_for_status() - + token_data = response.json() self._access_token = token_data["access_token"] - + if "refresh_token" in token_data: self._refresh_token = token_data["refresh_token"] - - self._token_expires_at = _get_token_expiration(self._access_token) if self._access_token is not None else None - + + self._token_expires_at = ( + _get_token_expiration(self._access_token) if self._access_token is not None else None + ) + logger.debug("Access token refreshed successfully") return True - + except Exception as e: logger.warning(f"Failed to refresh token: {e}") return False - def _authenticate(self) -> None: + async def _authenticate(self) -> None: """Obtiene un nuevo token de autenticación""" logger.debug("Authenticating with API") try: - token_data = get_auth_token(self.api_base_url, self.api_username, self.api_password) - + token_data = await get_auth_token(self.api_base_url, self.api_username, self.api_password) + self._access_token = token_data["access_token"] self._refresh_token = token_data.get("refresh_token") - - self._token_expires_at = _get_token_expiration(self._access_token) if self._access_token is not None else None - + + self._token_expires_at = ( + _get_token_expiration(self._access_token) if self._access_token is not None else None + ) + if self._token_expires_at: time_to_expire = self._token_expires_at - dt.datetime.now() logger.debug(f"Token expires at {self._token_expires_at} (in {time_to_expire})") else: logger.debug("Token expiration not found in JWT, using 1 hour default") self._token_expires_at = dt.datetime.now() + dt.timedelta(hours=1) - + logger.debug("Authentication successful") except Exception as e: logger.error(f"Authentication failed: {e}") @@ -334,33 +327,29 @@ def _authenticate(self) -> None: self._token_expires_at = None raise - def _get_client(self) -> httpx.Client: + async def _get_client(self) -> httpx.AsyncClient: """Obtiene un cliente HTTP autenticado, reutilizando la conexión si es posible""" - if not self._is_token_valid(): - if self._refresh_token and not self._refresh_access_token(): - self._authenticate() - elif not self._refresh_token: - self._authenticate() - - if self._client: - self._client.close() - self._client = None + async with self._lock: + if not self._is_token_valid(): + if self._refresh_token and not await self._refresh_access_token(): + await self._authenticate() + elif not self._refresh_token: + await self._authenticate() + + if self._client: + await self._client.aclose() + self._client = None + + if self._client is None: + url_parse = urllib.parse.urlparse(self.api_base_url) + url = url_parse.geturl() + auth = {"Authorization": f"Bearer {self._access_token}"} + self._client = httpx.AsyncClient(base_url=url, headers=auth, timeout=TIMEOUT, verify=SSL_CONTEXT) + logger.debug("Created new HTTP client") - if self._client is None: - url_parse = urllib.parse.urlparse(self.api_base_url) - url = url_parse.geturl() - auth = {"Authorization": f"Bearer {self._access_token}"} - self._client = httpx.Client( - base_url=url, - headers=auth, - timeout=TIMEOUT, - verify=SSL_CONTEXT - ) - logger.debug("Created new HTTP client") - return self._client - def _handle_auth_error(self, error: httpx.HTTPStatusError) -> None: + async def _handle_auth_error(self, error: httpx.HTTPStatusError) -> None: """Maneja errores de autenticación forzando una nueva autenticación""" if error.response.status_code == 401: logger.warning("Received 401, forcing re-authentication") @@ -368,27 +357,31 @@ def _handle_auth_error(self, error: httpx.HTTPStatusError) -> None: self._refresh_token = None self._token_expires_at = None if self._client: - self._client.close() + await self._client.aclose() self._client = None - def fetch_schedule_usage_records_large_interval( - self, since: dt.datetime, until: dt.datetime, meter_serial: Optional[str] = None, frt_code: Optional[str] = None + async def fetch_schedule_usage_records_large_interval( + self, + since: dt.datetime, + until: dt.datetime, + meter_serial: Optional[str] = None, + frt_code: Optional[str] = None, ) -> list[ScheduleUsageRecord]: _validate_query_parameters(frt_code, meter_serial) - + number_of_requests = math.ceil((until - since) / MAX_REQUEST_RANGE) logger.debug(f"Fetching usages in {number_of_requests} requests") usage_records = [] - + for i in range(0, number_of_requests): fi = since + i * MAX_REQUEST_RANGE ff = min(fi + MAX_REQUEST_RANGE, until) - + max_retries = 2 for attempt in range(max_retries): try: - client = self._get_client() - this_usage_records = get_schedule_usage_records( + client = await self._get_client() + this_usage_records = await get_schedule_usage_records( client, frt_code=frt_code, since=fi, until=ff, meter_serial=meter_serial ) usage_records.extend(this_usage_records) @@ -396,31 +389,35 @@ def fetch_schedule_usage_records_large_interval( except httpx.HTTPStatusError as e: if e.response.status_code == 401 and attempt < max_retries - 1: logger.warning(f"Authentication error on attempt {attempt + 1}, retrying...") - self._handle_auth_error(e) + await self._handle_auth_error(e) continue else: raise - + return usage_records - def fetch_schedule_measurements_records_large_interval( - self, since: dt.datetime, until: dt.datetime, meter_serial: Optional[str] = None, frt_code: Optional[str] = None + async def fetch_schedule_measurements_records_large_interval( + self, + since: dt.datetime, + until: dt.datetime, + meter_serial: Optional[str] = None, + frt_code: Optional[str] = None, ) -> list[ScheduleMeasurementRecord]: _validate_query_parameters(frt_code, meter_serial) - + number_of_requests = math.ceil((until - since) / MAX_REQUEST_RANGE) logger.debug(f"Fetching schedules in {number_of_requests} requests") schedule_records = [] - + for i in range(0, number_of_requests): fi = since + i * MAX_REQUEST_RANGE ff = min(fi + MAX_REQUEST_RANGE, until) - + max_retries = 2 for attempt in range(max_retries): try: - client = self._get_client() - this_schedule_records = get_schedule_measurement_records( + client = await self._get_client() + this_schedule_records = await get_schedule_measurement_records( client, frt_code=frt_code, since=fi, until=ff, meter_serial=meter_serial ) schedule_records.extend(this_schedule_records) @@ -428,24 +425,24 @@ def fetch_schedule_measurements_records_large_interval( except httpx.HTTPStatusError as e: if e.response.status_code == 401 and attempt < max_retries - 1: logger.warning(f"Authentication error on attempt {attempt + 1}, retrying...") - self._handle_auth_error(e) + await self._handle_auth_error(e) continue else: raise - + return schedule_records - def close(self) -> None: + async def close(self) -> None: """Cierra la conexión HTTP explícitamente""" if self._client: - self._client.close() + await self._client.aclose() self._client = None logger.debug("HTTP client closed") - def __enter__(self): + async def __aenter__(self): """Context manager entry""" return self - def __exit__(self, exc_type, exc_val, exc_tb): + async def __aexit__(self, exc_type, exc_val, exc_tb): """Context manager exit - cierra la conexión automáticamente""" - self.close() + await self.close() diff --git a/src/enerbitdso/formats.py b/src/enerbitdso/formats.py index 52ca667..7e8044d 100644 --- a/src/enerbitdso/formats.py +++ b/src/enerbitdso/formats.py @@ -15,6 +15,8 @@ def as_json(records: list[pydantic.BaseModel]) -> io.StringIO: def as_csv(records: typing.Sequence[pydantic.BaseModel], header: bool) -> io.StringIO: res = io.StringIO(newline="") + if not records: + return res fields = records[0].model_fields.keys() writer = csv.DictWriter(res, fields, lineterminator="\n") if header: diff --git a/tests/mocked_responses.py b/tests/mocked_responses.py index f0b2eed..79d8cf6 100644 --- a/tests/mocked_responses.py +++ b/tests/mocked_responses.py @@ -11,17 +11,13 @@ mocked_schedules: List[ScheduleMeasurementRecord] = [] -def create_mocked_schedules( - frt_code: str, since: dt.datetime, until: dt.datetime -) -> None: +def create_mocked_schedules(frt_code: str, since: dt.datetime, until: dt.datetime) -> None: mocked_schedules.clear() - dt_range: pd.core.indexes.datetimes.DatetimeIndex = ( - pd.core.indexes.datetimes.date_range( - since, - until, - inclusive="both", - freq="1H", - ) + dt_range: pd.DatetimeIndex = pd.date_range( + since, + until, + inclusive="both", + freq="1h", ) intervals = pd.DataFrame({"start": dt_range}) list_interval = intervals.to_dict(orient="records") @@ -33,7 +29,7 @@ def create_mocked_schedules( active_energy_exported = 0 reactive_energy_imported = 0 reactive_energy_exported = 0 - for index, item in enumerate(list_interval): + for _index, item in enumerate(list_interval): active_energy_imported += round(random.randint(0, 100)) active_energy_exported += round(random.randint(0, 100)) reactive_energy_imported += round(random.randint(0, 100)) @@ -60,40 +56,38 @@ def get_mocked_schedules( ) -> list[ScheduleMeasurementRecord]: """Mock function that handles both frt_code and meter_serial parameters""" filtered_mocked_schedules = [] - + for schedule in mocked_schedules: # Filter by time range if since and schedule.time_local_utc < since: continue if until and schedule.time_local_utc > until: continue - + # Filter by frt_code or meter_serial if frt_code and schedule.frt_code != frt_code: continue if meter_serial and schedule.meter_serial != meter_serial: continue - + filtered_mocked_schedules.append(schedule) - + return filtered_mocked_schedules def create_mocked_usages(frt_code: str, since: dt.datetime, until: dt.datetime) -> None: mocked_usages.clear() - dt_range: pd.core.indexes.datetimes.DatetimeIndex = ( - pd.core.indexes.datetimes.date_range( - since, - until - dt.timedelta(hours=1), - inclusive="both", - freq="1h", - ) + dt_range: pd.DatetimeIndex = pd.date_range( + since, + until - dt.timedelta(hours=1), + inclusive="both", + freq="1h", ) intervals = pd.DataFrame({"start": dt_range}) list_interval = intervals.to_dict(orient="records") letters = string.ascii_lowercase meter_serial = "".join(random.choice(letters) for i in range(10)) - for index, item in enumerate(list_interval): + for _index, item in enumerate(list_interval): mocked_usages.append( ScheduleUsageRecord.model_validate( { @@ -110,25 +104,23 @@ def create_mocked_usages(frt_code: str, since: dt.datetime, until: dt.datetime) ) -def get_mocked_usages( - ebclient, frt_code=None, since=None, until=None, meter_serial=None -) -> list[ScheduleUsageRecord]: +def get_mocked_usages(ebclient, frt_code=None, since=None, until=None, meter_serial=None) -> list[ScheduleUsageRecord]: """Mock function that handles both frt_code and meter_serial parameters""" filtered_mocked_usages = [] - + for usage in mocked_usages: # Filter by time range if since and usage.time_start < since: continue if until and usage.time_end > until: continue - + # Filter by frt_code or meter_serial if frt_code and usage.frt_code != frt_code: continue if meter_serial and usage.meter_serial != meter_serial: continue - + filtered_mocked_usages.append(usage) - + return filtered_mocked_usages diff --git a/tests/test_cli_usages.py b/tests/test_cli_usages.py index 8d25293..e9eb662 100644 --- a/tests/test_cli_usages.py +++ b/tests/test_cli_usages.py @@ -1,5 +1,6 @@ import os from unittest.mock import patch + from typer.testing import CliRunner from enerbitdso.cli import cli @@ -12,26 +13,19 @@ def test_usages_with_frt_list(): """Test CLI with FRT list (requires valid environment variables)""" # Solo ejecutar si las variables de entorno están configuradas - if not all([ - os.getenv("ENERBIT_API_BASE_URL"), - os.getenv("ENERBIT_API_USERNAME"), - os.getenv("ENERBIT_API_PASSWORD") - ]): + if not all( + [os.getenv("ENERBIT_API_BASE_URL"), os.getenv("ENERBIT_API_USERNAME"), os.getenv("ENERBIT_API_PASSWORD")] + ): return # Skip test if env vars not set - - command = USAGES_ARGLIST + [ - "fetch", - "--since=2023-01-01", - "--until=2023-01-02", - "Frt00000" - ] - + + command = USAGES_ARGLIST + ["fetch", "--since=2023-01-01", "--until=2023-01-02", "Frt00000"] + with patch("enerbitdso.enerbit.get_schedule_usage_records") as mock_get: mock_get.return_value = [] # Return empty list to avoid actual API calls - + with patch("enerbitdso.enerbit.get_auth_token") as mock_auth: mock_auth.return_value = {"access_token": "fake_token"} - + result = runner.invoke(cli, command) # Should not exit with error (even if no data returned) assert result.exit_code == 0 or "Failed to fetch" in result.output diff --git a/tests/test_lib.py b/tests/test_lib.py index 105cbd1..22f87d3 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -1,7 +1,7 @@ import datetime as dt import random import unittest -from unittest.mock import patch +from unittest.mock import AsyncMock, patch from enerbitdso.enerbit import ( DSOClient, @@ -9,8 +9,8 @@ ) from .mocked_responses import ( - create_mocked_usages, create_mocked_schedules, + create_mocked_usages, get_mocked_schedules, get_mocked_usages, mocked_schedules, @@ -20,9 +20,9 @@ WEEKS_TO_TEST = 5 -class TestMyLibrary(unittest.TestCase): - @patch("enerbitdso.enerbit.get_auth_token") - def test_get_all_usage_records(self, mock_get_auth_token): +class TestMyLibrary(unittest.IsolatedAsyncioTestCase): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_get_all_usage_records(self, mock_get_auth_token): today = dt.datetime.now().replace(minute=0, second=0, microsecond=0) since_month = today - dt.timedelta(weeks=WEEKS_TO_TEST) until_month = today @@ -31,7 +31,7 @@ def test_get_all_usage_records(self, mock_get_auth_token): mock_get_auth_token.return_value = { "access_token": "fake_access_token", "refresh_token": "fake_refresh_token", - "token_type": "bearer" + "token_type": "bearer", } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", @@ -40,9 +40,10 @@ def test_get_all_usage_records(self, mock_get_auth_token): ) with patch( "enerbitdso.enerbit.get_schedule_usage_records", + new_callable=AsyncMock, side_effect=get_mocked_usages, ): - usages = ebconnector.fetch_schedule_usage_records_large_interval( + usages = await ebconnector.fetch_schedule_usage_records_large_interval( frt_code=frontier, since=since_month, until=until_month ) print(f"🔍 DEBUG: Created {len(mocked_usages)} mocked usages") @@ -50,8 +51,8 @@ def test_get_all_usage_records(self, mock_get_auth_token): print(f"🔍 DEBUG: Created {len(usages)} usages") self.assertEqual(usages, mocked_usages) - @patch("enerbitdso.enerbit.get_auth_token") - def test_get_part_usage_records(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_get_part_usage_records(self, mock_get_auth_token): today = dt.datetime.now() since_month = today - dt.timedelta(weeks=WEEKS_TO_TEST) until_month = today @@ -60,10 +61,10 @@ def test_get_part_usage_records(self, mock_get_auth_token): frontier = "Frt" + "".join(random.choices("0123456789", k=5)) create_mocked_schedules(frt_code=frontier, since=since_month, until=until_month) mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", @@ -71,18 +72,17 @@ def test_get_part_usage_records(self, mock_get_auth_token): ) with patch( "enerbitdso.enerbit.get_schedule_usage_records", + new_callable=AsyncMock, side_effect=get_mocked_usages, ): - usages = ebconnector.fetch_schedule_usage_records_large_interval( + usages = await ebconnector.fetch_schedule_usage_records_large_interval( frt_code=frontier, since=since, until=until ) for usage in usages: - self.assertIn( - usage, mocked_usages, "The usage is not in mocked usages list" - ) + self.assertIn(usage, mocked_usages, "The usage is not in mocked usages list") - @patch("enerbitdso.enerbit.get_auth_token") - def test_get_empty_usage_records(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_get_empty_usage_records(self, mock_get_auth_token): today = dt.datetime.now() since_month = today - dt.timedelta(weeks=WEEKS_TO_TEST) until_month = today @@ -91,10 +91,10 @@ def test_get_empty_usage_records(self, mock_get_auth_token): frontier = "Frt" + "".join(random.choices("0123456789", k=5)) create_mocked_schedules(frt_code=frontier, since=since_month, until=until_month) mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", @@ -102,25 +102,26 @@ def test_get_empty_usage_records(self, mock_get_auth_token): ) with patch( "enerbitdso.enerbit.get_schedule_usage_records", + new_callable=AsyncMock, side_effect=get_mocked_usages, ): - usages = ebconnector.fetch_schedule_usage_records_large_interval( + usages = await ebconnector.fetch_schedule_usage_records_large_interval( frt_code=frontier, since=since, until=until ) self.assertEqual(usages, []) - @patch("enerbitdso.enerbit.get_auth_token") - def test_get_all_schedule_records(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_get_all_schedule_records(self, mock_get_auth_token): today = dt.datetime.now() since_month = today - dt.timedelta(weeks=WEEKS_TO_TEST) until_month = today frontier = "Frt" + "".join(random.choices("0123456789", k=5)) create_mocked_usages(frt_code=frontier, since=since_month, until=until_month) mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", @@ -128,15 +129,16 @@ def test_get_all_schedule_records(self, mock_get_auth_token): ) with patch( "enerbitdso.enerbit.get_schedule_measurement_records", + new_callable=AsyncMock, side_effect=get_mocked_schedules, ): - schedules = ebconnector.fetch_schedule_measurements_records_large_interval( + schedules = await ebconnector.fetch_schedule_measurements_records_large_interval( frt_code=frontier, since=since_month, until=until_month ) self.assertEqual(schedules, mocked_schedules) - @patch("enerbitdso.enerbit.get_auth_token") - def test_get_part_schedule_records(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_get_part_schedule_records(self, mock_get_auth_token): today = dt.datetime.now() since_month = today - dt.timedelta(weeks=WEEKS_TO_TEST) until_month = today @@ -145,10 +147,10 @@ def test_get_part_schedule_records(self, mock_get_auth_token): frontier = "Frt" + "".join(random.choices("0123456789", k=5)) create_mocked_usages(frt_code=frontier, since=since_month, until=until_month) mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", @@ -156,18 +158,17 @@ def test_get_part_schedule_records(self, mock_get_auth_token): ) with patch( "enerbitdso.enerbit.get_schedule_measurement_records", + new_callable=AsyncMock, side_effect=get_mocked_schedules, ): - schedules = ebconnector.fetch_schedule_measurements_records_large_interval( + schedules = await ebconnector.fetch_schedule_measurements_records_large_interval( frt_code=frontier, since=since, until=until ) for schedule in schedules: - self.assertIn( - schedule, mocked_schedules, "The schedule is not in mocked usages list" - ) + self.assertIn(schedule, mocked_schedules, "The schedule is not in mocked usages list") - @patch("enerbitdso.enerbit.get_auth_token") - def test_get_empty_schedule_records(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_get_empty_schedule_records(self, mock_get_auth_token): today = dt.datetime.now() since_month = today - dt.timedelta(weeks=WEEKS_TO_TEST) until_month = today @@ -176,10 +177,10 @@ def test_get_empty_schedule_records(self, mock_get_auth_token): frontier = "Frt" + "".join(random.choices("0123456789", k=5)) create_mocked_usages(frt_code=frontier, since=since_month, until=until_month) mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", @@ -187,125 +188,127 @@ def test_get_empty_schedule_records(self, mock_get_auth_token): ) with patch( "enerbitdso.enerbit.get_schedule_measurement_records", + new_callable=AsyncMock, side_effect=get_mocked_schedules, ): - schedules = ebconnector.fetch_schedule_measurements_records_large_interval( + schedules = await ebconnector.fetch_schedule_measurements_records_large_interval( frt_code=frontier, since=since, until=until ) self.assertEqual(schedules, []) - @patch("enerbitdso.enerbit.get_auth_token") - def test_parameter_validation_both_parameters(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_parameter_validation_both_parameters(self, mock_get_auth_token): """Test that providing both frt_code and meter_serial raises InvalidParameterError""" today = dt.datetime.now() yesterday = today - dt.timedelta(days=1) frontier = "Frt" + "".join(random.choices("0123456789", k=5)) meter = "".join(random.choices("0123456789", k=5)) - + mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", api_password="test", ) - + with self.assertRaises(InvalidParameterError) as context: - ebconnector.fetch_schedule_usage_records_large_interval( + await ebconnector.fetch_schedule_usage_records_large_interval( frt_code=frontier, meter_serial=meter, since=yesterday, until=today ) - + self.assertIn("No se pueden especificar tanto", str(context.exception)) - @patch("enerbitdso.enerbit.get_auth_token") - def test_parameter_validation_no_parameters(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_parameter_validation_no_parameters(self, mock_get_auth_token): """Test that providing neither frt_code nor meter_serial raises InvalidParameterError""" today = dt.datetime.now() yesterday = today - dt.timedelta(days=1) - + mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", api_password="test", ) - + with self.assertRaises(InvalidParameterError) as context: - ebconnector.fetch_schedule_usage_records_large_interval( + await ebconnector.fetch_schedule_usage_records_large_interval( frt_code=None, meter_serial=None, since=yesterday, until=today ) - + self.assertIn("Debe especificar al menos uno", str(context.exception)) - @patch("enerbitdso.enerbit.get_auth_token") - def test_parameter_validation_empty_strings(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_parameter_validation_empty_strings(self, mock_get_auth_token): """Test that empty strings are treated as None""" today = dt.datetime.now() yesterday = today - dt.timedelta(days=1) - + mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", api_password="test", ) - + with self.assertRaises(InvalidParameterError) as context: - ebconnector.fetch_schedule_usage_records_large_interval( + await ebconnector.fetch_schedule_usage_records_large_interval( frt_code="", meter_serial=" ", since=yesterday, until=today ) - + self.assertIn("Debe especificar al menos uno", str(context.exception)) - @patch("enerbitdso.enerbit.get_auth_token") - def test_meter_serial_filtering(self, mock_get_auth_token): + @patch("enerbitdso.enerbit.get_auth_token", new_callable=AsyncMock) + async def test_meter_serial_filtering(self, mock_get_auth_token): """Test filtering by meter_serial parameter""" today = dt.datetime.now() since_month = today - dt.timedelta(weeks=WEEKS_TO_TEST) until_month = today frontier = "Frt" + "".join(random.choices("0123456789", k=5)) test_meter = "12345" - + # Create mocked data create_mocked_usages(frt_code=frontier, since=since_month, until=until_month) # Modify first few records to have our test meter serial for i in range(min(3, len(mocked_usages))): mocked_usages[i].meter_serial = test_meter - + mock_get_auth_token.return_value = { - "access_token": "fake_access_token", - "refresh_token": "fake_refresh_token", - "token_type": "bearer" - } + "access_token": "fake_access_token", + "refresh_token": "fake_refresh_token", + "token_type": "bearer", + } ebconnector = DSOClient( api_base_url="https://dso.enerbit.me/", api_username="test", api_password="test", ) - + with patch( "enerbitdso.enerbit.get_schedule_usage_records", + new_callable=AsyncMock, side_effect=get_mocked_usages, ): - usages = ebconnector.fetch_schedule_usage_records_large_interval( + usages = await ebconnector.fetch_schedule_usage_records_large_interval( frt_code=None, meter_serial=test_meter, since=since_month, until=until_month ) - + # Verify all returned records have the correct meter_serial for usage in usages: self.assertEqual(usage.meter_serial, test_meter) - + # Should have exactly 3 records (the ones we modified) self.assertEqual(len(usages), 3) diff --git a/uv.lock b/uv.lock index 2c802a0..4a55811 100644 --- a/uv.lock +++ b/uv.lock @@ -1,63 +1,71 @@ version = 1 -revision = 3 requires-python = "==3.10.*" +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, +] + [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 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" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup" }, { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592 }, ] [[package]] name = "certifi" -version = "2024.8.30" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684 }, ] [[package]] name = "click" -version = "8.1.7" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 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" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "enerbitdso" +version = "0.1.21" source = { editable = "." } dependencies = [ { name = "httpx" }, @@ -102,170 +110,198 @@ dev = [ [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740 }, ] [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196, upload-time = "2024-11-15T12:30:47.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551, upload-time = "2024-11-15T12:30:45.782Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, ] [[package]] name = "httpx" -version = "0.27.2" +version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, - { name = "sniffio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] [[package]] name = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, ] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/5f/63f5fa395c7a8a93558c0904ba8f1c8d1b997ca6a3de61bc7659970d66bf/librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc", size = 65697 }, + { url = "https://files.pythonhosted.org/packages/ff/e0/0472cf37267b5920eff2f292ccfaede1886288ce35b7f3203d8de00abfe6/librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7", size = 68376 }, + { url = "https://files.pythonhosted.org/packages/c8/be/8bd1359fdcd27ab897cd5963294fa4a7c83b20a8564678e4fd12157e56a5/librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6", size = 197084 }, + { url = "https://files.pythonhosted.org/packages/e2/fe/163e33fdd091d0c2b102f8a60cc0a61fd730ad44e32617cd161e7cd67a01/librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0", size = 207337 }, + { url = "https://files.pythonhosted.org/packages/01/99/f85130582f05dcf0c8902f3d629270231d2f4afdfc567f8305a952ac7f14/librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b", size = 219980 }, + { url = "https://files.pythonhosted.org/packages/6f/54/cb5e4d03659e043a26c74e08206412ac9a3742f0477d96f9761a55313b5f/librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6", size = 212921 }, + { url = "https://files.pythonhosted.org/packages/b1/81/a3a01e4240579c30f3487f6fed01eb4bc8ef0616da5b4ebac27ca19775f3/librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71", size = 221381 }, + { url = "https://files.pythonhosted.org/packages/08/b0/fc2d54b4b1c6fb81e77288ff31ff25a2c1e62eaef4424a984f228839717b/librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7", size = 216714 }, + { url = "https://files.pythonhosted.org/packages/96/96/85daa73ffbd87e1fb287d7af6553ada66bf25a2a6b0de4764344a05469f6/librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05", size = 214777 }, + { url = "https://files.pythonhosted.org/packages/12/9c/c3aa7a2360383f4bf4f04d98195f2739a579128720c603f4807f006a4225/librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891", size = 237398 }, + { url = "https://files.pythonhosted.org/packages/61/19/d350ea89e5274665185dabc4bbb9c3536c3411f862881d316c8b8e00eb66/librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7", size = 54285 }, + { url = "https://files.pythonhosted.org/packages/4f/d6/45d587d3d41c112e9543a0093d883eb57a24a03e41561c127818aa2a6bcc/librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2", size = 61352 }, ] [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 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" }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] [[package]] name = "mypy" -version = "1.13.0" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, + { name = "pathspec" }, { name = "tomli" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532, upload-time = "2024-10-22T21:55:47.458Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/8c/206de95a27722b5b5a8c85ba3100467bd86299d92a4f71c6b9aa448bfa2f/mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a", size = 11020731, upload-time = "2024-10-22T21:54:54.221Z" }, - { url = "https://files.pythonhosted.org/packages/ab/bb/b31695a29eea76b1569fd28b4ab141a1adc9842edde080d1e8e1776862c7/mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80", size = 10184276, upload-time = "2024-10-22T21:54:34.679Z" }, - { url = "https://files.pythonhosted.org/packages/a5/2d/4a23849729bb27934a0e079c9c1aad912167d875c7b070382a408d459651/mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7", size = 12587706, upload-time = "2024-10-22T21:55:45.309Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c3/d318e38ada50255e22e23353a469c791379825240e71b0ad03e76ca07ae6/mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f", size = 13105586, upload-time = "2024-10-22T21:55:18.957Z" }, - { url = "https://files.pythonhosted.org/packages/4a/25/3918bc64952370c3dbdbd8c82c363804678127815febd2925b7273d9482c/mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372", size = 9632318, upload-time = "2024-10-22T21:55:13.791Z" }, - { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043, upload-time = "2024-10-22T21:55:16.617Z" }, + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333 }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102 }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799 }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149 }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105 }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200 }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239 }, ] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, ] [[package]] name = "numpy" -version = "2.1.3" +version = "2.2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090, upload-time = "2024-11-02T17:48:55.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/80/d572a4737626372915bca41c3afbfec9d173561a39a0a61bacbbfd1dafd4/numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", size = 21152472, upload-time = "2024-11-02T17:30:37.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bb/7bfba10c791ae3bb6716da77ad85a82d5fac07fc96fb0023ef0571df9d20/numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", size = 13747967, upload-time = "2024-11-02T17:30:59.602Z" }, - { url = "https://files.pythonhosted.org/packages/da/d6/2df7bde35f0478455f0be5934877b3e5a505f587b00230f54a519a6b55a5/numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", size = 5354921, upload-time = "2024-11-02T17:31:09.428Z" }, - { url = "https://files.pythonhosted.org/packages/d1/bb/75b945874f931494891eac6ca06a1764d0e8208791f3addadb2963b83527/numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", size = 6888603, upload-time = "2024-11-02T17:31:20.835Z" }, - { url = "https://files.pythonhosted.org/packages/68/a7/fde73636f6498dbfa6d82fc336164635fe592f1ad0d13285fcb6267fdc1c/numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", size = 13889862, upload-time = "2024-11-02T17:31:41.486Z" }, - { url = "https://files.pythonhosted.org/packages/05/db/5d9c91b2e1e2e72be1369278f696356d44975befcae830daf2e667dcb54f/numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", size = 16328151, upload-time = "2024-11-02T17:32:08.262Z" }, - { url = "https://files.pythonhosted.org/packages/3e/6a/7eb732109b53ae64a29e25d7e68eb9d6611037f6354875497008a49e74d3/numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", size = 16704107, upload-time = "2024-11-02T17:32:34.361Z" }, - { url = "https://files.pythonhosted.org/packages/88/cc/278113b66a1141053cbda6f80e4200c6da06b3079c2d27bda1fde41f2c1f/numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4", size = 14385789, upload-time = "2024-11-02T17:32:57.152Z" }, - { url = "https://files.pythonhosted.org/packages/f5/69/eb20f5e1bfa07449bc67574d2f0f7c1e6b335fb41672e43861a7727d85f2/numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", size = 6536706, upload-time = "2024-11-02T17:33:09.12Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8b/1c131ab5a94c1086c289c6e1da1d843de9dbd95fe5f5ee6e61904c9518e2/numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", size = 12864165, upload-time = "2024-11-02T17:33:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/00/e7/8d8bb791b62586cc432ecbb70632b4f23b7b7c88df41878de7528264f6d7/numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", size = 20983893, upload-time = "2024-11-02T17:47:09.365Z" }, - { url = "https://files.pythonhosted.org/packages/5e/f3/cb8118a044b5007586245a650360c9f5915b2f4232dd7658bb7a63dd1d02/numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", size = 6752501, upload-time = "2024-11-02T17:47:21.52Z" }, - { url = "https://files.pythonhosted.org/packages/53/f5/365b46439b518d2ec6ebb880cc0edf90f225145dfd4db7958334f7164530/numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", size = 16142601, upload-time = "2024-11-02T17:47:45.575Z" }, - { url = "https://files.pythonhosted.org/packages/03/c2/d1fee6ba999aa7cd41ca6856937f2baaf604c3eec1565eae63451ec31e5e/numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", size = 12771397, upload-time = "2024-11-02T17:48:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245 }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048 }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542 }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301 }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320 }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050 }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034 }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185 }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149 }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620 }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391 }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754 }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476 }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666 }, ] [[package]] name = "orjson" -version = "3.10.11" +version = "3.11.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/3a/10320029954badc7eaa338a15ee279043436f396e965dafc169610e4933f/orjson-3.10.11.tar.gz", hash = "sha256:e35b6d730de6384d5b2dab5fd23f0d76fae8bbc8c353c2f78210aa5fa4beb3ef", size = 5444879, upload-time = "2024-11-02T00:52:10.129Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/63/f7d412e09f6e2c4e2562ddc44e86f2316a7ce9d7f353afa7cbce4f6a78d5/orjson-3.10.11-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6dade64687f2bd7c090281652fe18f1151292d567a9302b34c2dbb92a3872f1f", size = 266434, upload-time = "2024-11-02T00:50:45.751Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6a/3dfcd3a8c0e588581c8d1f3d9002cca970432da8a8096c1a42b99914a34d/orjson-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f07c550a6ccd2b9290849b22316a609023ed851a87ea888c0456485a7d196a", size = 151884, upload-time = "2024-11-02T00:50:48.738Z" }, - { url = "https://files.pythonhosted.org/packages/41/02/8981bc5ccbc04a2bd49cd86224d5b1e2c7417fb33e83590c66c3a028ede5/orjson-3.10.11-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd9a187742d3ead9df2e49240234d728c67c356516cf4db018833a86f20ec18c", size = 167371, upload-time = "2024-11-02T00:50:51.47Z" }, - { url = "https://files.pythonhosted.org/packages/df/3f/772a12a417444eccc54fa597955b689848eb121d5e43dd7da9f6658c314d/orjson-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77b0fed6f209d76c1c39f032a70df2d7acf24b1812ca3e6078fd04e8972685a3", size = 154367, upload-time = "2024-11-02T00:50:54.689Z" }, - { url = "https://files.pythonhosted.org/packages/8a/63/d0d6ba28410ec603fc31726a49dc782c72c0a64f4cd0a6734a6d8bc07a4a/orjson-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63fc9d5fe1d4e8868f6aae547a7b8ba0a2e592929245fff61d633f4caccdcdd6", size = 165726, upload-time = "2024-11-02T00:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/97/6e/d291bf382173af7788b368e4c22d02c7bdb9b7ac29b83e92930841321c16/orjson-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65cd3e3bb4fbb4eddc3c1e8dce10dc0b73e808fcb875f9fab40c81903dd9323e", size = 142522, upload-time = "2024-11-02T00:50:58.84Z" }, - { url = "https://files.pythonhosted.org/packages/6d/3b/7364c10fcadf7c08e3658fe7103bf3b0408783f91022be4691fbe0b5ba1d/orjson-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f67c570602300c4befbda12d153113b8974a3340fdcf3d6de095ede86c06d92", size = 146931, upload-time = "2024-11-02T00:51:01.919Z" }, - { url = "https://files.pythonhosted.org/packages/95/8c/43f454e642cc85ef845cda6efcfddc6b5fe46b897b692412022012e1272c/orjson-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f39728c7f7d766f1f5a769ce4d54b5aaa4c3f92d5b84817053cc9995b977acc", size = 142900, upload-time = "2024-11-02T00:51:04.044Z" }, - { url = "https://files.pythonhosted.org/packages/bb/29/ca24efe043501b4a4584d728fdc65af5cfc070ab9021a07fb195bce98919/orjson-3.10.11-cp310-none-win32.whl", hash = "sha256:1789d9db7968d805f3d94aae2c25d04014aae3a2fa65b1443117cd462c6da647", size = 144456, upload-time = "2024-11-01T23:52:30.448Z" }, - { url = "https://files.pythonhosted.org/packages/b7/ec/f15dc012928459cfb96ed86178d92fddb5c01847f2c53fd8be2fa62dee6c/orjson-3.10.11-cp310-none-win_amd64.whl", hash = "sha256:5576b1e5a53a5ba8f8df81872bb0878a112b3ebb1d392155f00f54dd86c83ff6", size = 136442, upload-time = "2024-11-01T23:48:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/de/1a/a373746fa6d0e116dd9e54371a7b54622c44d12296d5d0f3ad5e3ff33490/orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174", size = 229140 }, + { url = "https://files.pythonhosted.org/packages/52/a2/fa129e749d500f9b183e8a3446a193818a25f60261e9ce143ad61e975208/orjson-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63c6e6738d7c3470ad01601e23376aa511e50e1f3931395b9f9c722406d1a67", size = 128670 }, + { url = "https://files.pythonhosted.org/packages/08/93/1e82011cd1e0bd051ef9d35bed1aa7fb4ea1f0a055dc2c841b46b43a9ebd/orjson-3.11.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043d3006b7d32c7e233b8cfb1f01c651013ea079e08dcef7189a29abd8befe11", size = 123832 }, + { url = "https://files.pythonhosted.org/packages/fe/d8/a26b431ef962c7d55736674dddade876822f3e33223c1f47a36879350d04/orjson-3.11.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57036b27ac8a25d81112eb0cc9835cd4833c5b16e1467816adc0015f59e870dc", size = 129171 }, + { url = "https://files.pythonhosted.org/packages/a7/19/f47819b84a580f490da260c3ee9ade214cf4cf78ac9ce8c1c758f80fdfc9/orjson-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:733ae23ada68b804b222c44affed76b39e30806d38660bf1eb200520d259cc16", size = 141967 }, + { url = "https://files.pythonhosted.org/packages/5b/cd/37ece39a0777ba077fdcdbe4cccae3be8ed00290c14bf8afdc548befc260/orjson-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fdfad2093bdd08245f2e204d977facd5f871c88c4a71230d5bcbd0e43bf6222", size = 130991 }, + { url = "https://files.pythonhosted.org/packages/8f/ed/f2b5d66aa9b6b5c02ff5f120efc7b38c7c4962b21e6be0f00fd99a5c348e/orjson-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cededd6738e1c153530793998e31c05086582b08315db48ab66649768f326baa", size = 133674 }, + { url = "https://files.pythonhosted.org/packages/c4/6e/baa83e68d1aa09fa8c3e5b2c087d01d0a0bd45256de719ed7bc22c07052d/orjson-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:14f440c7268c8f8633d1b3d443a434bd70cb15686117ea6beff8fdc8f5917a1e", size = 138722 }, + { url = "https://files.pythonhosted.org/packages/0c/47/7f8ef4963b772cd56999b535e553f7eb5cd27e9dd6c049baee6f18bfa05d/orjson-3.11.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3a2479753bbb95b0ebcf7969f562cdb9668e6d12416a35b0dda79febf89cdea2", size = 409056 }, + { url = "https://files.pythonhosted.org/packages/38/eb/2df104dd2244b3618f25325a656f85cc3277f74bbd91224752410a78f3c7/orjson-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:71924496986275a737f38e3f22b4e0878882b3f7a310d2ff4dc96e812789120c", size = 144196 }, + { url = "https://files.pythonhosted.org/packages/b6/2a/ee41de0aa3a6686598661eae2b4ebdff1340c65bfb17fcff8b87138aab21/orjson-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4a9eefdc70bf8bf9857f0290f973dec534ac84c35cd6a7f4083be43e7170a8f", size = 134979 }, + { url = "https://files.pythonhosted.org/packages/4c/fa/92fc5d3d402b87a8b28277a9ed35386218a6a5287c7fe5ee9b9f02c53fb2/orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de", size = 127968 }, + { url = "https://files.pythonhosted.org/packages/07/29/a576bf36d73d60df06904d3844a9df08e25d59eba64363aaf8ec2f9bff41/orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993", size = 125128 }, ] [[package]] name = "packaging" -version = "24.2" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366 }, ] [[package]] name = "pandas" -version = "2.2.3" +version = "2.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -273,109 +309,129 @@ dependencies = [ { name = "pytz" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223 } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827, upload-time = "2024-09-20T13:08:42.347Z" }, - { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897, upload-time = "2024-09-20T13:08:45.807Z" }, - { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908, upload-time = "2024-09-20T18:37:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210, upload-time = "2024-09-20T13:08:48.325Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292, upload-time = "2024-09-20T19:01:54.443Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379, upload-time = "2024-09-20T13:08:50.882Z" }, - { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471, upload-time = "2024-09-20T13:08:53.332Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763 }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217 }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791 }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373 }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444 }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459 }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086 }, ] [[package]] name = "pandas-stubs" -version = "2.2.3.241009" +version = "2.3.3.260113" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "types-pytz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/30/1ca31098512cdcfbc6ce366072848dff497880d4285281606b5895244bbc/pandas_stubs-2.2.3.241009.tar.gz", hash = "sha256:d4ab618253f0acf78a5d0d2bfd6dffdd92d91a56a69bdc8144e5a5c6d25be3b5", size = 103801, upload-time = "2024-10-09T14:55:57.276Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/5d/be23854a73fda69f1dbdda7bc10fbd6f930bd1fa87aaec389f00c901c1e8/pandas_stubs-2.3.3.260113.tar.gz", hash = "sha256:076e3724bcaa73de78932b012ec64b3010463d377fa63116f4e6850643d93800", size = 116131 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c6/df1fe324248424f77b89371116dab5243db7f052c32cc9fe7442ad9c5f75/pandas_stubs-2.3.3.260113-py3-none-any.whl", hash = "sha256:ec070b5c576e1badf12544ae50385872f0631fc35d99d00dc598c2954ec564d3", size = 168246 }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/be/d9ba3109c4c19a78e125f63074c4e436e447f30ece15f0ef1865e7178233/pandas_stubs-2.2.3.241009-py3-none-any.whl", hash = "sha256:3a6f8f142105a42550be677ba741ba532621f4e0acad2155c0e7b2450f114cfa", size = 157883, upload-time = "2024-10-09T14:55:54.415Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206 }, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] [[package]] name = "pydantic" -version = "2.9.2" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917, upload-time = "2024-09-17T15:59:54.273Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591 } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928, upload-time = "2024-09-17T15:59:51.827Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580 }, ] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156, upload-time = "2024-09-16T16:06:44.786Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835, upload-time = "2024-09-16T16:03:57.223Z" }, - { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689, upload-time = "2024-09-16T16:03:59.266Z" }, - { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748, upload-time = "2024-09-16T16:04:01.011Z" }, - { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469, upload-time = "2024-09-16T16:04:02.323Z" }, - { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246, upload-time = "2024-09-16T16:04:03.688Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404, upload-time = "2024-09-16T16:04:05.299Z" }, - { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940, upload-time = "2024-09-16T16:04:06.604Z" }, - { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437, upload-time = "2024-09-16T16:04:08.071Z" }, - { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129, upload-time = "2024-09-16T16:04:10.363Z" }, - { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908, upload-time = "2024-09-16T16:04:12.412Z" }, - { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278, upload-time = "2024-09-16T16:04:13.732Z" }, - { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453, upload-time = "2024-09-16T16:04:15.996Z" }, - { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135, upload-time = "2024-09-16T16:06:10.45Z" }, - { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583, upload-time = "2024-09-16T16:06:12.298Z" }, - { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637, upload-time = "2024-09-16T16:06:14.092Z" }, - { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963, upload-time = "2024-09-16T16:06:16.757Z" }, - { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332, upload-time = "2024-09-16T16:06:18.677Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926, upload-time = "2024-09-16T16:06:20.591Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342, upload-time = "2024-09-16T16:06:22.888Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344, upload-time = "2024-09-16T16:06:24.849Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298 }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475 }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815 }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567 }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442 }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956 }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253 }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050 }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178 }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833 }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156 }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378 }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622 }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441 }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291 }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632 }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905 }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495 }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388 }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879 }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017 }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351 }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363 }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615 }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369 }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218 }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951 }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428 }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009 }, ] [[package]] name = "pydantic-settings" -version = "2.6.1" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646, upload-time = "2024-11-01T11:00:05.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595, upload-time = "2024-11-01T11:00:02.64Z" }, + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929 }, ] [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905, upload-time = "2024-05-04T13:42:02.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513, upload-time = "2024-05-04T13:41:57.345Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] [[package]] name = "pytest" -version = "8.3.3" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -383,11 +439,12 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, { name = "tomli" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487, upload-time = "2024-09-10T10:52:15.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341, upload-time = "2024-09-10T10:52:12.54Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 }, ] [[package]] @@ -397,151 +454,153 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] [[package]] name = "python-dotenv" -version = "1.0.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101 }, ] [[package]] name = "pytz" -version = "2024.2" +version = "2026.1.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692, upload-time = "2024-09-11T02:24:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002, upload-time = "2024-09-11T02:24:45.8Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489 }, ] [[package]] name = "rich" -version = "13.9.4" +version = "14.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458 }, ] [[package]] name = "ruff" -version = "0.7.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/8b/bc4e0dfa1245b07cf14300e10319b98e958a53ff074c1dd86b35253a8c2a/ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2", size = 3275547, upload-time = "2024-11-15T11:33:11.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/4b/f5094719e254829766b807dadb766841124daba75a37da83e292ae5ad12f/ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478", size = 10447512, upload-time = "2024-11-15T11:32:27.812Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1d/3d2d2c9f601cf6044799c5349ff5267467224cefed9b35edf5f1f36486e9/ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63", size = 10235436, upload-time = "2024-11-15T11:32:30.6Z" }, - { url = "https://files.pythonhosted.org/packages/62/83/42a6ec6216ded30b354b13e0e9327ef75a3c147751aaf10443756cb690e9/ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20", size = 9888936, upload-time = "2024-11-15T11:32:33.287Z" }, - { url = "https://files.pythonhosted.org/packages/4d/26/e1e54893b13046a6ad05ee9b89ee6f71542ba250f72b4c7a7d17c3dbf73d/ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109", size = 10697353, upload-time = "2024-11-15T11:32:35.895Z" }, - { url = "https://files.pythonhosted.org/packages/21/24/98d2e109c4efc02bfef144ec6ea2c3e1217e7ce0cfddda8361d268dfd499/ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452", size = 10228078, upload-time = "2024-11-15T11:32:40.929Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b7/964c75be9bc2945fc3172241b371197bb6d948cc69e28bc4518448c368f3/ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea", size = 11264823, upload-time = "2024-11-15T11:32:43.31Z" }, - { url = "https://files.pythonhosted.org/packages/12/8d/20abdbf705969914ce40988fe71a554a918deaab62c38ec07483e77866f6/ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7", size = 11951855, upload-time = "2024-11-15T11:32:46.038Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fc/6519ce58c57b4edafcdf40920b7273dfbba64fc6ebcaae7b88e4dc1bf0a8/ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05", size = 11516580, upload-time = "2024-11-15T11:32:48.17Z" }, - { url = "https://files.pythonhosted.org/packages/37/1a/5ec1844e993e376a86eb2456496831ed91b4398c434d8244f89094758940/ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06", size = 12692057, upload-time = "2024-11-15T11:32:50.623Z" }, - { url = "https://files.pythonhosted.org/packages/50/90/76867152b0d3c05df29a74bb028413e90f704f0f6701c4801174ba47f959/ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc", size = 11085137, upload-time = "2024-11-15T11:32:52.819Z" }, - { url = "https://files.pythonhosted.org/packages/c8/eb/0a7cb6059ac3555243bd026bb21785bbc812f7bbfa95a36c101bd72b47ae/ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172", size = 10681243, upload-time = "2024-11-15T11:32:55.902Z" }, - { url = "https://files.pythonhosted.org/packages/5e/76/2270719dbee0fd35780b05c08a07b7a726c3da9f67d9ae89ef21fc18e2e5/ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a", size = 10319187, upload-time = "2024-11-15T11:32:58.255Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e5/39100f72f8ba70bec1bd329efc880dea8b6c1765ea1cb9d0c1c5f18b8d7f/ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd", size = 10803715, upload-time = "2024-11-15T11:33:00.88Z" }, - { url = "https://files.pythonhosted.org/packages/a5/89/40e904784f305fb56850063f70a998a64ebba68796d823dde67e89a24691/ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a", size = 11162912, upload-time = "2024-11-15T11:33:03.097Z" }, - { url = "https://files.pythonhosted.org/packages/8d/1b/dd77503b3875c51e3dbc053fd8367b845ab8b01c9ca6d0c237082732856c/ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac", size = 8702767, upload-time = "2024-11-15T11:33:05.15Z" }, - { url = "https://files.pythonhosted.org/packages/63/76/253ddc3e89e70165bba952ecca424b980b8d3c2598ceb4fc47904f424953/ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6", size = 9497534, upload-time = "2024-11-15T11:33:07.359Z" }, - { url = "https://files.pythonhosted.org/packages/aa/70/f8724f31abc0b329ca98b33d73c14020168babcf71b0cba3cded5d9d0e66/ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f", size = 8851590, upload-time = "2024-11-15T11:33:09.664Z" }, +version = "0.15.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953 }, + { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257 }, + { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683 }, + { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986 }, + { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177 }, + { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783 }, + { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201 }, + { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561 }, + { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928 }, + { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186 }, + { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231 }, + { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357 }, + { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583 }, + { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976 }, + { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872 }, + { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271 }, + { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497 }, ] [[package]] name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 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" }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, ] [[package]] name = "six" -version = "1.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041, upload-time = "2021-05-05T14:18:18.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053, upload-time = "2021-05-05T14:18:17.237Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } 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" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] [[package]] name = "tomli" -version = "2.1.0" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/e4/1b6cbcc82d8832dd0ce34767d5c560df8a3547ad8cbc427f34601415930a/tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8", size = 16622, upload-time = "2024-11-11T18:38:01.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/f7/4da0ffe1892122c9ea096c57f64c2753ae5dd3ce85488802d11b0992cc6d/tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391", size = 13750, upload-time = "2024-11-11T18:38:00.19Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477 }, ] [[package]] name = "truststore" -version = "0.10.0" +version = "0.10.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/a8/cdcf418e067b8ae539012a3f1a51f90b30b26f5e1952a8f60304396babbc/truststore-0.10.0.tar.gz", hash = "sha256:5da347c665714fdfbd46f738c823fe9f0d8775e41ac5fb94f325749091187896", size = 24810, upload-time = "2024-10-23T22:11:21.193Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/c9/bab5a5dda14af36fe31e2215f0f87bf34408951972fa7220055926dab2e0/truststore-0.10.0-py3-none-any.whl", hash = "sha256:b3798548e421ffe2ca2a6217cca49e7a17baf40b72d86a5505dc7d701e77d15b", size = 18231, upload-time = "2024-10-23T22:11:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660 }, ] [[package]] name = "typer" -version = "0.13.1" +version = "0.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "annotated-doc" }, { name = "click" }, { name = "rich" }, { name = "shellingham" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/41/49ead3ad3310545e767bcb917c759b026ca9eada5d5c150c2fb6fc555568/typer-0.13.1.tar.gz", hash = "sha256:9d444cb96cc268ce6f8b94e13b4335084cef4c079998a9f4851a90229a3bd25c", size = 98631, upload-time = "2024-11-18T22:39:19.727Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613 } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/69/e90a0b4d0c16e095901679216c8ecdc728110c7c54e7b5f43a623bc4c789/typer-0.13.1-py3-none-any.whl", hash = "sha256:5b59580fd925e89463a29d363e0a43245ec02765bde9fb77d39e5d0f29dd7157", size = 44723, upload-time = "2024-11-18T22:39:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085 }, ] [[package]] name = "types-pytz" -version = "2024.2.0.20241003" +version = "2026.1.1.20260304" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/d0/73aa3063a9ef9881bd7103cb4ae379bfd8fafda0e86b01b694d676313a4b/types-pytz-2024.2.0.20241003.tar.gz", hash = "sha256:575dc38f385a922a212bac00a7d6d2e16e141132a3c955078f4a4fd13ed6cb44", size = 5474, upload-time = "2024-10-03T02:43:30.322Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/56/2f12a15ea8c5615c8fb896c4fbbb527ab1c0f776ed5860c6fc9ec26ea2c7/types_pytz-2026.1.1.20260304.tar.gz", hash = "sha256:0c3542d8e9b0160b424233440c52b83d6f58cae4b85333d54e4f961cf013e117", size = 11198 } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/60/2a2977ce0f91255bbb668350b127a801a06ad37c326a2e5bfd52f03e0784/types_pytz-2024.2.0.20241003-py3-none-any.whl", hash = "sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7", size = 5245, upload-time = "2024-10-03T02:43:29.492Z" }, + { url = "https://files.pythonhosted.org/packages/94/b8/e77c355f179dc89d44e7ca6dbf7a46e650806df1d356a5462e5829fccea5/types_pytz-2026.1.1.20260304-py3-none-any.whl", hash = "sha256:175332c1cf7bd6b1cc56b877f70bf02def1a3f75e5adcc05385ce2c3c70e6500", size = 10126 }, ] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +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 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, ] [[package]] name = "tzdata" -version = "2024.2" +version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282, upload-time = "2024-09-23T18:56:46.89Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586, upload-time = "2024-09-23T18:56:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, ]