From 5e2e57edb855eabfc29f45076d891019204795c3 Mon Sep 17 00:00:00 2001 From: John Forstmeier Date: Fri, 13 Mar 2026 11:01:41 -0400 Subject: [PATCH 1/6] Broad repository reorganization --- .github/workflows/launch_infrastructure.yaml | 12 +- Cargo.lock | 2 +- Cargo.toml | 2 +- .../{datamanager => data_manager}/Cargo.toml | 6 +- .../{datamanager => data_manager}/Dockerfile | 15 +- .../{datamanager => data_manager}/bacon.toml | 0 .../{datamanager => data_manager}/src/data.rs | 0 .../src/equity_bars.rs | 0 .../src/equity_details.rs | 0 .../src/errors.rs | 0 .../src/health.rs | 0 .../{datamanager => data_manager}/src/lib.rs | 0 .../{datamanager => data_manager}/src/main.rs | 4 +- .../src/portfolios.rs | 0 .../src/predictions.rs | 0 .../src/router.rs | 0 .../src/startup.rs | 4 +- .../src/state.rs | 0 .../src/storage.rs | 0 .../tests/common/mod.rs | 0 .../tests/test_data.rs | 2 +- .../tests/test_errors.rs | 2 +- .../tests/test_handlers.rs | 2 +- .../tests/test_state_and_health.rs | 2 +- .../tests/test_storage.rs | 2 +- applications/ensemble_manager/Dockerfile | 33 + .../pyproject.toml | 8 +- .../ensemble_manager}/predictions_schema.py | 0 .../src/ensemble_manager}/preprocess.py | 0 .../src/ensemble_manager}/server.py | 11 +- .../tests/conftest.py | 0 .../tests/test_predictions_schema.py | 2 +- .../tests/test_preprocess.py | 2 +- .../tests/test_server.py | 12 +- applications/equitypricemodel/Dockerfile | 62 - .../Dockerfile | 9 +- .../pyproject.toml | 2 +- .../src/portfolio_manager}/alpaca_client.py | 0 .../src/portfolio_manager}/beta.py | 0 .../src/portfolio_manager}/consolidation.py | 0 .../src/portfolio_manager}/data_client.py | 0 .../src/portfolio_manager}/enums.py | 0 .../src/portfolio_manager}/exceptions.py | 0 .../portfolio_manager}/portfolio_schema.py | 0 .../src/portfolio_manager}/regime.py | 0 .../src/portfolio_manager}/report.py | 0 .../src/portfolio_manager}/risk_management.py | 0 .../src/portfolio_manager}/server.py | 2 +- .../statistical_arbitrage.py | 0 .../tests/test_alpaca_client.py | 40 +- .../tests/test_beta.py | 2 +- .../tests/test_consolidation.py | 2 +- .../tests/test_data_client.py | 40 +- .../tests/test_portfolio_schema.py | 2 +- .../tests/test_portfolio_server.py | 26 +- .../tests/test_regime.py | 2 +- .../tests/test_report.py | 2 +- .../tests/test_risk_management.py | 8 +- .../tests/test_statistical_arbitrage.py | 2 +- libraries/python/pyproject.toml | 2 +- .../python/src/internal}/combine_data.py | 16 +- .../src/internal}/equity_details_schema.py | 0 .../tests/test_equity_details_schema.py | 2 +- {tools/src/tools/flows => models}/__init__.py | 0 models/tide/Dockerfile | 43 + models/tide/__init__.py | 0 models/tide/pyproject.toml | 26 + models/tide/src/tide/__init__.py | 0 .../tide/src/tide/deploy.py | 8 +- .../tide/src/tide}/notifications.py | 4 +- .../tide/src/tide/run.py | 8 +- .../tide/src/tide/tasks.py | 36 +- .../tide/src/tide}/tide_data.py | 0 .../tide/src/tide}/tide_model.py | 0 .../tide/src/tide}/trainer.py | 60 +- .../tide/src/tide/workflow.py | 16 +- models/tide/tests/__init__.py | 0 models/tide/tests/conftest.py | 51 + .../tide/tests/test_deploy.py | 6 +- .../tide}/tests/test_notifications.py | 42 +- .../tide/tests/test_run.py | 12 +- .../tide/tests/test_tasks.py | 4 +- .../tide}/tests/test_tide_data.py | 2 +- .../tide}/tests/test_tide_model.py | 2 +- .../tide}/tests/test_trainer.py | 2 +- .../tide/tests/test_workflow.py | 28 +- pyproject.toml | 12 +- tools/pyproject.toml | 1 - uv.lock | 1679 +++++++++-------- 89 files changed, 1277 insertions(+), 1109 deletions(-) rename applications/{datamanager => data_manager}/Cargo.toml (95%) rename applications/{datamanager => data_manager}/Dockerfile (81%) rename applications/{datamanager => data_manager}/bacon.toml (100%) rename applications/{datamanager => data_manager}/src/data.rs (100%) rename applications/{datamanager => data_manager}/src/equity_bars.rs (100%) rename applications/{datamanager => data_manager}/src/equity_details.rs (100%) rename applications/{datamanager => data_manager}/src/errors.rs (100%) rename applications/{datamanager => data_manager}/src/health.rs (100%) rename applications/{datamanager => data_manager}/src/lib.rs (100%) rename applications/{datamanager => data_manager}/src/main.rs (93%) rename applications/{datamanager => data_manager}/src/portfolios.rs (100%) rename applications/{datamanager => data_manager}/src/predictions.rs (100%) rename applications/{datamanager => data_manager}/src/router.rs (100%) rename applications/{datamanager => data_manager}/src/startup.rs (98%) rename applications/{datamanager => data_manager}/src/state.rs (100%) rename applications/{datamanager => data_manager}/src/storage.rs (100%) rename applications/{datamanager => data_manager}/tests/common/mod.rs (100%) rename applications/{datamanager => data_manager}/tests/test_data.rs (99%) rename applications/{datamanager => data_manager}/tests/test_errors.rs (93%) rename applications/{datamanager => data_manager}/tests/test_handlers.rs (99%) rename applications/{datamanager => data_manager}/tests/test_state_and_health.rs (99%) rename applications/{datamanager => data_manager}/tests/test_storage.rs (99%) create mode 100644 applications/ensemble_manager/Dockerfile rename applications/{equitypricemodel => ensemble_manager}/pyproject.toml (75%) rename applications/{equitypricemodel/src/equitypricemodel => ensemble_manager/src/ensemble_manager}/predictions_schema.py (100%) rename applications/{equitypricemodel/src/equitypricemodel => ensemble_manager/src/ensemble_manager}/preprocess.py (100%) rename applications/{equitypricemodel/src/equitypricemodel => ensemble_manager/src/ensemble_manager}/server.py (98%) rename applications/{equitypricemodel => ensemble_manager}/tests/conftest.py (100%) rename applications/{equitypricemodel => ensemble_manager}/tests/test_predictions_schema.py (98%) rename applications/{equitypricemodel => ensemble_manager}/tests/test_preprocess.py (98%) rename applications/{equitypricemodel => ensemble_manager}/tests/test_server.py (86%) delete mode 100644 applications/equitypricemodel/Dockerfile rename applications/{portfoliomanager => portfolio_manager}/Dockerfile (56%) rename applications/{portfoliomanager => portfolio_manager}/pyproject.toml (94%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/alpaca_client.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/beta.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/consolidation.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/data_client.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/enums.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/exceptions.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/portfolio_schema.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/regime.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/report.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/risk_management.py (100%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/server.py (99%) rename applications/{portfoliomanager/src/portfoliomanager => portfolio_manager/src/portfolio_manager}/statistical_arbitrage.py (100%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_alpaca_client.py (88%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_beta.py (98%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_consolidation.py (99%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_data_client.py (84%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_portfolio_schema.py (99%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_portfolio_server.py (92%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_regime.py (99%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_report.py (99%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_risk_management.py (96%) rename applications/{portfoliomanager => portfolio_manager}/tests/test_statistical_arbitrage.py (99%) rename {applications/equitypricemodel/src/equitypricemodel => libraries/python/src/internal}/combine_data.py (84%) rename {applications/equitypricemodel/src/equitypricemodel => libraries/python/src/internal}/equity_details_schema.py (100%) rename {applications/equitypricemodel => libraries/python}/tests/test_equity_details_schema.py (98%) rename {tools/src/tools/flows => models}/__init__.py (100%) create mode 100644 models/tide/Dockerfile create mode 100644 models/tide/__init__.py create mode 100644 models/tide/pyproject.toml create mode 100644 models/tide/src/tide/__init__.py rename tools/src/tools/deploy_training_flow.py => models/tide/src/tide/deploy.py (87%) rename {tools/src/tools/flows => models/tide/src/tide}/notifications.py (94%) rename tools/src/tools/run_training_job.py => models/tide/src/tide/run.py (87%) rename tools/src/tools/prepare_training_data.py => models/tide/src/tide/tasks.py (83%) rename {applications/equitypricemodel/src/equitypricemodel => models/tide/src/tide}/tide_data.py (100%) rename {applications/equitypricemodel/src/equitypricemodel => models/tide/src/tide}/tide_model.py (100%) rename {applications/equitypricemodel/src/equitypricemodel => models/tide/src/tide}/trainer.py (71%) rename tools/src/tools/flows/training_flow.py => models/tide/src/tide/workflow.py (93%) create mode 100644 models/tide/tests/__init__.py create mode 100644 models/tide/tests/conftest.py rename tools/tests/test_deploy_training_flow.py => models/tide/tests/test_deploy.py (88%) rename {tools => models/tide}/tests/test_notifications.py (76%) rename tools/tests/test_run_training_job.py => models/tide/tests/test_run.py (85%) rename tools/tests/test_prepare_training_data.py => models/tide/tests/test_tasks.py (97%) rename {applications/equitypricemodel => models/tide}/tests/test_tide_data.py (99%) rename {applications/equitypricemodel => models/tide}/tests/test_tide_model.py (99%) rename {applications/equitypricemodel => models/tide}/tests/test_trainer.py (96%) rename tools/tests/test_training_flow.py => models/tide/tests/test_workflow.py (85%) diff --git a/.github/workflows/launch_infrastructure.yaml b/.github/workflows/launch_infrastructure.yaml index abcc6e78a..9dcf7b153 100644 --- a/.github/workflows/launch_infrastructure.yaml +++ b/.github/workflows/launch_infrastructure.yaml @@ -24,12 +24,12 @@ jobs: strategy: matrix: include: - - service: datamanager - paths: applications/datamanager/** - - service: portfoliomanager - paths: applications/portfoliomanager/** - - service: equitypricemodel - paths: applications/equitypricemodel/** + - service: data_manager + paths: applications/data_manager/** + - service: portfolio_manager + paths: applications/portfolio_manager/** + - service: ensemble_manager + paths: applications/ensemble_manager/** steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/Cargo.lock b/Cargo.lock index ffc2119fc..fa5b9c359 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1570,7 +1570,7 @@ dependencies = [ ] [[package]] -name = "datamanager" +name = "data_manager" version = "0.0.1" dependencies = [ "aws-config", diff --git a/Cargo.toml b/Cargo.toml index 172eebc90..ebc7ed9ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["applications/datamanager"] +members = ["applications/data_manager"] diff --git a/applications/datamanager/Cargo.toml b/applications/data_manager/Cargo.toml similarity index 95% rename from applications/datamanager/Cargo.toml rename to applications/data_manager/Cargo.toml index 925ff2ac4..012b70d37 100644 --- a/applications/datamanager/Cargo.toml +++ b/applications/data_manager/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "datamanager" +name = "data_manager" version = "0.0.1" edition = "2021" [lib] -name = "datamanager" +name = "data_manager" path = "src/lib.rs" [[bin]] -name = "datamanager" +name = "data_manager" path = "src/main.rs" [dependencies] diff --git a/applications/datamanager/Dockerfile b/applications/data_manager/Dockerfile similarity index 81% rename from applications/datamanager/Dockerfile rename to applications/data_manager/Dockerfile index cc7563204..8261ed0d7 100644 --- a/applications/datamanager/Dockerfile +++ b/applications/data_manager/Dockerfile @@ -31,7 +31,7 @@ FROM chef AS planner COPY Cargo.toml Cargo.lock ./ -COPY applications/datamanager/Cargo.toml ./applications/datamanager/Cargo.toml +COPY applications/data_manager/Cargo.toml ./applications/data_manager/Cargo.toml RUN cargo chef prepare --recipe-path recipe.json @@ -45,9 +45,9 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ COPY Cargo.toml Cargo.lock ./ -COPY applications/datamanager/Cargo.toml ./applications/datamanager/Cargo.toml +COPY applications/data_manager/Cargo.toml ./applications/data_manager/Cargo.toml -COPY applications/datamanager/src/ applications/datamanager/src/ +COPY applications/data_manager/src/ applications/data_manager/src/ ENV DUCKDB_LIB_DIR=/usr/local/lib ENV DUCKDB_INCLUDE_DIR=/usr/local/include @@ -56,8 +56,8 @@ ENV LD_LIBRARY_PATH=/usr/local/lib RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/app/target \ - cargo build --release --bin datamanager && \ - cp /app/target/release/datamanager /tmp/datamanager + cargo build --release --bin data_manager && \ + cp /app/target/release/data_manager /tmp/data_manager FROM debian:trixie-slim AS server @@ -77,9 +77,8 @@ ENV SSL_CERT_DIR=/etc/ssl/certs WORKDIR /app -COPY --from=builder /tmp/datamanager /usr/local/bin/datamanager +COPY --from=builder /tmp/data_manager /usr/local/bin/data_manager EXPOSE 8080 -ENTRYPOINT ["/usr/local/bin/datamanager"] - +ENTRYPOINT ["/usr/local/bin/data_manager"] diff --git a/applications/datamanager/bacon.toml b/applications/data_manager/bacon.toml similarity index 100% rename from applications/datamanager/bacon.toml rename to applications/data_manager/bacon.toml diff --git a/applications/datamanager/src/data.rs b/applications/data_manager/src/data.rs similarity index 100% rename from applications/datamanager/src/data.rs rename to applications/data_manager/src/data.rs diff --git a/applications/datamanager/src/equity_bars.rs b/applications/data_manager/src/equity_bars.rs similarity index 100% rename from applications/datamanager/src/equity_bars.rs rename to applications/data_manager/src/equity_bars.rs diff --git a/applications/datamanager/src/equity_details.rs b/applications/data_manager/src/equity_details.rs similarity index 100% rename from applications/datamanager/src/equity_details.rs rename to applications/data_manager/src/equity_details.rs diff --git a/applications/datamanager/src/errors.rs b/applications/data_manager/src/errors.rs similarity index 100% rename from applications/datamanager/src/errors.rs rename to applications/data_manager/src/errors.rs diff --git a/applications/datamanager/src/health.rs b/applications/data_manager/src/health.rs similarity index 100% rename from applications/datamanager/src/health.rs rename to applications/data_manager/src/health.rs diff --git a/applications/datamanager/src/lib.rs b/applications/data_manager/src/lib.rs similarity index 100% rename from applications/datamanager/src/lib.rs rename to applications/data_manager/src/lib.rs diff --git a/applications/datamanager/src/main.rs b/applications/data_manager/src/main.rs similarity index 93% rename from applications/datamanager/src/main.rs rename to applications/data_manager/src/main.rs index 14d8aaa93..adc2f8145 100644 --- a/applications/datamanager/src/main.rs +++ b/applications/data_manager/src/main.rs @@ -1,4 +1,4 @@ -use datamanager::startup::{initialize_sentry, initialize_tracing, run_server}; +use data_manager::startup::{initialize_sentry, initialize_tracing, run_server}; async fn run_with_bind_address(bind_address: &str) -> i32 { let _sentry_guard = initialize_sentry(); @@ -54,7 +54,7 @@ mod tests { std::env::set_var("MASSIVE_API_KEY", "test-key"); std::env::set_var("SENTRY_DSN", ""); std::env::set_var("ENVIRONMENT", "test"); - std::env::set_var("RUST_LOG", "datamanager=debug,tower_http=debug"); + std::env::set_var("RUST_LOG", "data_manager=debug,tower_http=debug"); } let exit_code = run_with_bind_address("invalid-address").await; diff --git a/applications/datamanager/src/portfolios.rs b/applications/data_manager/src/portfolios.rs similarity index 100% rename from applications/datamanager/src/portfolios.rs rename to applications/data_manager/src/portfolios.rs diff --git a/applications/datamanager/src/predictions.rs b/applications/data_manager/src/predictions.rs similarity index 100% rename from applications/datamanager/src/predictions.rs rename to applications/data_manager/src/predictions.rs diff --git a/applications/datamanager/src/router.rs b/applications/data_manager/src/router.rs similarity index 100% rename from applications/datamanager/src/router.rs rename to applications/data_manager/src/router.rs diff --git a/applications/datamanager/src/startup.rs b/applications/data_manager/src/startup.rs similarity index 98% rename from applications/datamanager/src/startup.rs rename to applications/data_manager/src/startup.rs index b3aebe7cb..4e3c0bdd7 100644 --- a/applications/datamanager/src/startup.rs +++ b/applications/data_manager/src/startup.rs @@ -45,7 +45,7 @@ pub async fn serve_app(listener: TcpListener, app: Router) -> std::io::Result<() } pub async fn run_server(bind_address: &str) -> std::io::Result<()> { - tracing::info!("Starting datamanager service"); + tracing::info!("Starting data_manager service"); let app = create_app().await; let listener = TcpListener::bind(bind_address).await?; @@ -156,7 +156,7 @@ mod tests { let _environment_guard = EnvironmentVariableGuard::set("ENVIRONMENT", "test"); let _sentry_dsn_guard = EnvironmentVariableGuard::set("SENTRY_DSN", ""); let _rust_log_guard = - EnvironmentVariableGuard::set("RUST_LOG", "datamanager=debug,tower_http=debug"); + EnvironmentVariableGuard::set("RUST_LOG", "data_manager=debug,tower_http=debug"); let _sentry_guard = initialize_sentry(); let _ = initialize_tracing(); let _ = initialize_tracing(); diff --git a/applications/datamanager/src/state.rs b/applications/data_manager/src/state.rs similarity index 100% rename from applications/datamanager/src/state.rs rename to applications/data_manager/src/state.rs diff --git a/applications/datamanager/src/storage.rs b/applications/data_manager/src/storage.rs similarity index 100% rename from applications/datamanager/src/storage.rs rename to applications/data_manager/src/storage.rs diff --git a/applications/datamanager/tests/common/mod.rs b/applications/data_manager/tests/common/mod.rs similarity index 100% rename from applications/datamanager/tests/common/mod.rs rename to applications/data_manager/tests/common/mod.rs diff --git a/applications/datamanager/tests/test_data.rs b/applications/data_manager/tests/test_data.rs similarity index 99% rename from applications/datamanager/tests/test_data.rs rename to applications/data_manager/tests/test_data.rs index e30c788e4..3a70087a1 100644 --- a/applications/datamanager/tests/test_data.rs +++ b/applications/data_manager/tests/test_data.rs @@ -1,7 +1,7 @@ mod common; use common::initialize_test_tracing; -use datamanager::data::{ +use data_manager::data::{ create_equity_bar_dataframe, create_equity_details_dataframe, create_portfolio_dataframe, create_predictions_dataframe, EquityBar, Portfolio, Prediction, }; diff --git a/applications/datamanager/tests/test_errors.rs b/applications/data_manager/tests/test_errors.rs similarity index 93% rename from applications/datamanager/tests/test_errors.rs rename to applications/data_manager/tests/test_errors.rs index 8fa7552b4..9ec46477e 100644 --- a/applications/datamanager/tests/test_errors.rs +++ b/applications/data_manager/tests/test_errors.rs @@ -1,4 +1,4 @@ -use datamanager::errors::Error; +use data_manager::errors::Error; #[test] fn test_error_display_formats_messages() { diff --git a/applications/datamanager/tests/test_handlers.rs b/applications/data_manager/tests/test_handlers.rs similarity index 99% rename from applications/datamanager/tests/test_handlers.rs rename to applications/data_manager/tests/test_handlers.rs index 9a8cefd16..5165ebd78 100644 --- a/applications/datamanager/tests/test_handlers.rs +++ b/applications/data_manager/tests/test_handlers.rs @@ -1,6 +1,6 @@ mod common; -use datamanager::{ +use data_manager::{ router::create_app_with_state, state::{MassiveSecrets, State}, }; diff --git a/applications/datamanager/tests/test_state_and_health.rs b/applications/data_manager/tests/test_state_and_health.rs similarity index 99% rename from applications/datamanager/tests/test_state_and_health.rs rename to applications/data_manager/tests/test_state_and_health.rs index 6fd004d70..c117d6779 100644 --- a/applications/datamanager/tests/test_state_and_health.rs +++ b/applications/data_manager/tests/test_state_and_health.rs @@ -1,6 +1,6 @@ mod common; -use datamanager::{ +use data_manager::{ router::create_app_with_state, state::{MassiveSecrets, State}, }; diff --git a/applications/datamanager/tests/test_storage.rs b/applications/data_manager/tests/test_storage.rs similarity index 99% rename from applications/datamanager/tests/test_storage.rs rename to applications/data_manager/tests/test_storage.rs index c6388e540..fa6369c28 100644 --- a/applications/datamanager/tests/test_storage.rs +++ b/applications/data_manager/tests/test_storage.rs @@ -1,7 +1,7 @@ mod common; use chrono::{TimeZone, Utc}; -use datamanager::{ +use data_manager::{ data::{ create_equity_bar_dataframe, create_portfolio_dataframe, create_predictions_dataframe, EquityBar, Portfolio, Prediction, diff --git a/applications/ensemble_manager/Dockerfile b/applications/ensemble_manager/Dockerfile new file mode 100644 index 000000000..1ade78b24 --- /dev/null +++ b/applications/ensemble_manager/Dockerfile @@ -0,0 +1,33 @@ +FROM python:3.12.10-slim AS builder + +COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv + +WORKDIR /app + +COPY pyproject.toml uv.lock ./ + +COPY applications/ensemble_manager/ applications/ensemble_manager/ + +COPY libraries/python/ libraries/python/ + +COPY models/tide/ models/tide/ + +RUN uv sync --no-dev + +FROM python:3.12.10-slim AS server + +RUN apt-get update && \ + apt-get install -y --no-install-recommends build-essential clang && \ + rm -rf /var/lib/apt/lists/* + +ENV PYTHONPATH=/app/applications/ensemble_manager/src + +WORKDIR /app + +COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv + +COPY --from=builder /app /app + +EXPOSE 8080 + +ENTRYPOINT ["uv", "run", "--package", "ensemble_manager", "uvicorn", "ensemble_manager.server:application", "--host", "0.0.0.0", "--port", "8080", "--app-dir", "applications/ensemble_manager/src"] diff --git a/applications/equitypricemodel/pyproject.toml b/applications/ensemble_manager/pyproject.toml similarity index 75% rename from applications/equitypricemodel/pyproject.toml rename to applications/ensemble_manager/pyproject.toml index 846541f4d..77a191455 100644 --- a/applications/equitypricemodel/pyproject.toml +++ b/applications/ensemble_manager/pyproject.toml @@ -1,10 +1,11 @@ [project] -name = "equitypricemodel" +name = "ensemble_manager" version = "0.0.1" -description = "Equity price time-series dense encoder model architecture" +description = "Ensemble manager service for combining model predictions" requires-python = "==3.12.10" dependencies = [ "internal>=0.0.1", + "tide>=0.0.1", "boto3>=1.35.0", "fastapi>=0.115.0", "uvicorn>=0.34.0", @@ -13,8 +14,6 @@ dependencies = [ "requests>=2.32.5", "sentry-sdk[fastapi]>=2.0.0", "structlog>=25.5.0", - "tinygrad>=0.10.3", - "numpy>=1.26.4", ] [dependency-groups] @@ -22,6 +21,7 @@ dev = ["boto3-stubs[s3,ssm]>=1.38.0"] [tool.uv.sources] internal = { workspace = true } +tide = { workspace = true } [tool.uv] package = true diff --git a/applications/equitypricemodel/src/equitypricemodel/predictions_schema.py b/applications/ensemble_manager/src/ensemble_manager/predictions_schema.py similarity index 100% rename from applications/equitypricemodel/src/equitypricemodel/predictions_schema.py rename to applications/ensemble_manager/src/ensemble_manager/predictions_schema.py diff --git a/applications/equitypricemodel/src/equitypricemodel/preprocess.py b/applications/ensemble_manager/src/ensemble_manager/preprocess.py similarity index 100% rename from applications/equitypricemodel/src/equitypricemodel/preprocess.py rename to applications/ensemble_manager/src/ensemble_manager/preprocess.py diff --git a/applications/equitypricemodel/src/equitypricemodel/server.py b/applications/ensemble_manager/src/ensemble_manager/server.py similarity index 98% rename from applications/equitypricemodel/src/equitypricemodel/server.py rename to applications/ensemble_manager/src/ensemble_manager/server.py index 0d6439aab..ee43dda31 100644 --- a/applications/equitypricemodel/src/equitypricemodel/server.py +++ b/applications/ensemble_manager/src/ensemble_manager/server.py @@ -23,15 +23,16 @@ if TYPE_CHECKING: from mypy_boto3_s3 import S3Client -from .equity_details_schema import equity_details_schema +from internal.equity_details_schema import equity_details_schema +from tide.tide_data import Data +from tide.tide_model import Model + from .predictions_schema import predictions_schema from .preprocess import filter_equity_bars -from .tide_data import Data -from .tide_model import Model sentry_sdk.init( dsn=os.environ.get("SENTRY_DSN"), - environment=os.environ.get("ENVIRONMENT", "development"), + environment=os.environ.get("FUND_ENVIRONMENT", "development"), traces_sample_rate=1.0, profiles_sample_rate=1.0, enable_tracing=True, @@ -59,7 +60,7 @@ logger = structlog.get_logger() DATAMANAGER_BASE_URL = os.getenv("FUND_DATAMANAGER_BASE_URL", "http://datamanager:8080") -MODEL_VERSION_SSM_PARAMETER = "/fund/equitypricemodel/model_version" +MODEL_VERSION_SSM_PARAMETER = "/fund/ensemble_manager/model_version" def find_latest_artifact_key( diff --git a/applications/equitypricemodel/tests/conftest.py b/applications/ensemble_manager/tests/conftest.py similarity index 100% rename from applications/equitypricemodel/tests/conftest.py rename to applications/ensemble_manager/tests/conftest.py diff --git a/applications/equitypricemodel/tests/test_predictions_schema.py b/applications/ensemble_manager/tests/test_predictions_schema.py similarity index 98% rename from applications/equitypricemodel/tests/test_predictions_schema.py rename to applications/ensemble_manager/tests/test_predictions_schema.py index 496bb61a5..bad1f220d 100644 --- a/applications/equitypricemodel/tests/test_predictions_schema.py +++ b/applications/ensemble_manager/tests/test_predictions_schema.py @@ -2,7 +2,7 @@ import polars as pl import pytest -from equitypricemodel.predictions_schema import predictions_schema +from ensemble_manager.predictions_schema import predictions_schema from pandera.errors import SchemaError diff --git a/applications/equitypricemodel/tests/test_preprocess.py b/applications/ensemble_manager/tests/test_preprocess.py similarity index 98% rename from applications/equitypricemodel/tests/test_preprocess.py rename to applications/ensemble_manager/tests/test_preprocess.py index 7e2db0130..0b3b79ecb 100644 --- a/applications/equitypricemodel/tests/test_preprocess.py +++ b/applications/ensemble_manager/tests/test_preprocess.py @@ -1,5 +1,5 @@ import polars as pl -from equitypricemodel.preprocess import filter_equity_bars +from ensemble_manager.preprocess import filter_equity_bars def test_filter_equity_bars_above_thresholds() -> None: diff --git a/applications/equitypricemodel/tests/test_server.py b/applications/ensemble_manager/tests/test_server.py similarity index 86% rename from applications/equitypricemodel/tests/test_server.py rename to applications/ensemble_manager/tests/test_server.py index ea93e62b3..eb776abf4 100644 --- a/applications/equitypricemodel/tests/test_server.py +++ b/applications/ensemble_manager/tests/test_server.py @@ -1,7 +1,7 @@ from unittest.mock import MagicMock, patch from botocore.exceptions import ClientError -from equitypricemodel.server import _resolve_artifact_key +from ensemble_manager.server import _resolve_artifact_key def test_resolve_artifact_key_uses_latest_by_default() -> None: @@ -18,8 +18,8 @@ def test_resolve_artifact_key_uses_latest_by_default() -> None: ) with ( - patch("equitypricemodel.server.boto3") as mock_boto3, - patch("equitypricemodel.server.find_latest_artifact_key") as mock_find, + patch("ensemble_manager.server.boto3") as mock_boto3, + patch("ensemble_manager.server.find_latest_artifact_key") as mock_find, ): mock_boto3.client.return_value = mock_ssm mock_find.return_value = "artifacts/model-2026/output/model.tar.gz" @@ -40,7 +40,7 @@ def test_resolve_artifact_key_uses_ssm_version() -> None: "Parameter": {"Value": "equitypricemodel-trainer-2026-01-15"} } - with patch("equitypricemodel.server.boto3") as mock_boto3: + with patch("ensemble_manager.server.boto3") as mock_boto3: mock_boto3.client.return_value = mock_ssm result = _resolve_artifact_key( s3_client=mock_s3, @@ -59,7 +59,7 @@ def test_resolve_artifact_key_ssm_tar_gz_value() -> None: "Parameter": {"Value": "custom/path/model.tar.gz"} } - with patch("equitypricemodel.server.boto3") as mock_boto3: + with patch("ensemble_manager.server.boto3") as mock_boto3: mock_boto3.client.return_value = mock_ssm result = _resolve_artifact_key( s3_client=mock_s3, @@ -75,7 +75,7 @@ def test_resolve_artifact_key_explicit_tar_gz_path() -> None: mock_ssm = MagicMock() mock_ssm.get_parameter.return_value = {"Parameter": {"Value": "latest"}} - with patch("equitypricemodel.server.boto3") as mock_boto3: + with patch("ensemble_manager.server.boto3") as mock_boto3: mock_boto3.client.return_value = mock_ssm result = _resolve_artifact_key( s3_client=mock_s3, diff --git a/applications/equitypricemodel/Dockerfile b/applications/equitypricemodel/Dockerfile deleted file mode 100644 index e4fb44aca..000000000 --- a/applications/equitypricemodel/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -FROM python:3.12.10-slim AS builder - -COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv - -WORKDIR /app - -COPY pyproject.toml uv.lock ./ - -COPY applications/equitypricemodel/ applications/equitypricemodel/ - -COPY libraries/python/ libraries/python/ - -RUN uv sync --no-dev - -FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS trainer - -ENV DEBIAN_FRONTEND=noninteractive -ENV TZ=UTC - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - software-properties-common && \ - add-apt-repository -y ppa:deadsnakes/ppa && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - python3.12 python3.12-venv python3.12-dev \ - build-essential clang curl && \ - rm -rf /var/lib/apt/lists/* && \ - ln -sf /usr/bin/python3.12 /usr/bin/python - -ENV CUDA=1 - -WORKDIR /app - -COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv - -COPY --from=builder /app /app - -ENV PYTHONPATH=/app/applications/equitypricemodel/src -ENV TRAINING_DATA_PATH=/app/training-data/filtered_tide_training_data.parquet -ENV MODEL_OUTPUT_PATH=/app/model-artifacts - -ENTRYPOINT ["uv", "run", "--package", "equitypricemodel", "python", "-m", "equitypricemodel.trainer"] - -FROM python:3.12.10-slim AS server - -RUN apt-get update && \ - apt-get install -y --no-install-recommends build-essential clang && \ - rm -rf /var/lib/apt/lists/* - -ENV PYTHONPATH=/app/applications/equitypricemodel/src - -WORKDIR /app - -COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv - -COPY --from=builder /app /app - -EXPOSE 8080 - -ENTRYPOINT ["uv", "run", "--package", "equitypricemodel", "uvicorn", "equitypricemodel.server:application", "--host", "0.0.0.0", "--port", "8080", "--app-dir", "applications/equitypricemodel/src"] - diff --git a/applications/portfoliomanager/Dockerfile b/applications/portfolio_manager/Dockerfile similarity index 56% rename from applications/portfoliomanager/Dockerfile rename to applications/portfolio_manager/Dockerfile index ab7207020..6586c2def 100644 --- a/applications/portfoliomanager/Dockerfile +++ b/applications/portfolio_manager/Dockerfile @@ -6,11 +6,11 @@ WORKDIR /app COPY pyproject.toml uv.lock ./ -COPY applications/portfoliomanager/ applications/portfoliomanager/ +COPY applications/portfolio_manager/ applications/portfolio_manager/ COPY libraries/python/ libraries/python/ -RUN uv sync --no-dev +RUN uv sync --no-dev FROM python:3.12.10-slim AS server @@ -18,7 +18,7 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends build-essential clang && \ rm -rf /var/lib/apt/lists/* -ENV PYTHONPATH=/app/applications/portfoliomanager/src +ENV PYTHONPATH=/app/applications/portfolio_manager/src WORKDIR /app @@ -28,5 +28,4 @@ COPY --from=builder /app /app EXPOSE 8080 -ENTRYPOINT ["uv", "run", "--package", "portfoliomanager", "uvicorn", "portfoliomanager.server:application", "--host", "0.0.0.0", "--port", "8080", "--app-dir", "applications/portfoliomanager/src"] - +ENTRYPOINT ["uv", "run", "--package", "portfolio_manager", "uvicorn", "portfolio_manager.server:application", "--host", "0.0.0.0", "--port", "8080", "--app-dir", "applications/portfolio_manager/src"] diff --git a/applications/portfoliomanager/pyproject.toml b/applications/portfolio_manager/pyproject.toml similarity index 94% rename from applications/portfoliomanager/pyproject.toml rename to applications/portfolio_manager/pyproject.toml index 9de61b157..f94dd87f0 100644 --- a/applications/portfoliomanager/pyproject.toml +++ b/applications/portfolio_manager/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "portfoliomanager" +name = "portfolio_manager" version = "0.0.1" description = "Portfolio prediction and construction service" requires-python = "==3.12.10" diff --git a/applications/portfoliomanager/src/portfoliomanager/alpaca_client.py b/applications/portfolio_manager/src/portfolio_manager/alpaca_client.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/alpaca_client.py rename to applications/portfolio_manager/src/portfolio_manager/alpaca_client.py diff --git a/applications/portfoliomanager/src/portfoliomanager/beta.py b/applications/portfolio_manager/src/portfolio_manager/beta.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/beta.py rename to applications/portfolio_manager/src/portfolio_manager/beta.py diff --git a/applications/portfoliomanager/src/portfoliomanager/consolidation.py b/applications/portfolio_manager/src/portfolio_manager/consolidation.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/consolidation.py rename to applications/portfolio_manager/src/portfolio_manager/consolidation.py diff --git a/applications/portfoliomanager/src/portfoliomanager/data_client.py b/applications/portfolio_manager/src/portfolio_manager/data_client.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/data_client.py rename to applications/portfolio_manager/src/portfolio_manager/data_client.py diff --git a/applications/portfoliomanager/src/portfoliomanager/enums.py b/applications/portfolio_manager/src/portfolio_manager/enums.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/enums.py rename to applications/portfolio_manager/src/portfolio_manager/enums.py diff --git a/applications/portfoliomanager/src/portfoliomanager/exceptions.py b/applications/portfolio_manager/src/portfolio_manager/exceptions.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/exceptions.py rename to applications/portfolio_manager/src/portfolio_manager/exceptions.py diff --git a/applications/portfoliomanager/src/portfoliomanager/portfolio_schema.py b/applications/portfolio_manager/src/portfolio_manager/portfolio_schema.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/portfolio_schema.py rename to applications/portfolio_manager/src/portfolio_manager/portfolio_schema.py diff --git a/applications/portfoliomanager/src/portfoliomanager/regime.py b/applications/portfolio_manager/src/portfolio_manager/regime.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/regime.py rename to applications/portfolio_manager/src/portfolio_manager/regime.py diff --git a/applications/portfoliomanager/src/portfoliomanager/report.py b/applications/portfolio_manager/src/portfolio_manager/report.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/report.py rename to applications/portfolio_manager/src/portfolio_manager/report.py diff --git a/applications/portfoliomanager/src/portfoliomanager/risk_management.py b/applications/portfolio_manager/src/portfolio_manager/risk_management.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/risk_management.py rename to applications/portfolio_manager/src/portfolio_manager/risk_management.py diff --git a/applications/portfoliomanager/src/portfoliomanager/server.py b/applications/portfolio_manager/src/portfolio_manager/server.py similarity index 99% rename from applications/portfoliomanager/src/portfoliomanager/server.py rename to applications/portfolio_manager/src/portfolio_manager/server.py index e03798364..a831fcba7 100644 --- a/applications/portfoliomanager/src/portfoliomanager/server.py +++ b/applications/portfolio_manager/src/portfolio_manager/server.py @@ -17,7 +17,7 @@ sentry_sdk.init( dsn=os.environ.get("SENTRY_DSN"), - environment=os.environ.get("ENVIRONMENT", "development"), + environment=os.environ.get("FUND_ENVIRONMENT", "development"), traces_sample_rate=1.0, profiles_sample_rate=1.0, enable_tracing=True, diff --git a/applications/portfoliomanager/src/portfoliomanager/statistical_arbitrage.py b/applications/portfolio_manager/src/portfolio_manager/statistical_arbitrage.py similarity index 100% rename from applications/portfoliomanager/src/portfoliomanager/statistical_arbitrage.py rename to applications/portfolio_manager/src/portfolio_manager/statistical_arbitrage.py diff --git a/applications/portfoliomanager/tests/test_alpaca_client.py b/applications/portfolio_manager/tests/test_alpaca_client.py similarity index 88% rename from applications/portfoliomanager/tests/test_alpaca_client.py rename to applications/portfolio_manager/tests/test_alpaca_client.py index 5164edb3d..57644db25 100644 --- a/applications/portfoliomanager/tests/test_alpaca_client.py +++ b/applications/portfolio_manager/tests/test_alpaca_client.py @@ -3,9 +3,9 @@ import pytest from alpaca.trading.enums import AssetClass, AssetStatus from alpaca.trading.requests import GetAssetsRequest -from portfoliomanager.alpaca_client import AlpacaAccount, AlpacaClient -from portfoliomanager.enums import TradeSide -from portfoliomanager.exceptions import ( +from portfolio_manager.alpaca_client import AlpacaAccount, AlpacaClient +from portfolio_manager.enums import TradeSide +from portfolio_manager.exceptions import ( AssetNotShortableError, InsufficientBuyingPowerError, ) @@ -27,8 +27,8 @@ def __init__( def _make_client() -> tuple[AlpacaClient, MagicMock]: - trading_patch = patch("portfoliomanager.alpaca_client.TradingClient") - data_patch = patch("portfoliomanager.alpaca_client.StockHistoricalDataClient") + trading_patch = patch("portfolio_manager.alpaca_client.TradingClient") + data_patch = patch("portfolio_manager.alpaca_client.StockHistoricalDataClient") mock_trading_cls = trading_patch.start() data_patch.start() client = AlpacaClient(api_key="test", api_secret="test", is_paper=True) # noqa: S106 @@ -62,7 +62,7 @@ def test_alpaca_account_stores_values() -> None: assert account.buying_power == EXPECTED_BUYING_POWER -@patch("portfoliomanager.alpaca_client.time.sleep") +@patch("portfolio_manager.alpaca_client.time.sleep") def test_get_account_returns_account(mock_sleep: MagicMock) -> None: client, mock_trading = _make_client() mock_account = MagicMock() @@ -79,7 +79,7 @@ def test_get_account_returns_account(mock_sleep: MagicMock) -> None: mock_sleep.assert_called_once_with(client.rate_limit_sleep) -@patch("portfoliomanager.alpaca_client.time.sleep") +@patch("portfolio_manager.alpaca_client.time.sleep") def test_open_position_buy_submits_order(mock_sleep: MagicMock) -> None: client, mock_trading = _make_client() @@ -89,7 +89,7 @@ def test_open_position_buy_submits_order(mock_sleep: MagicMock) -> None: mock_sleep.assert_called_once_with(client.rate_limit_sleep) -@patch("portfoliomanager.alpaca_client.time.sleep") +@patch("portfolio_manager.alpaca_client.time.sleep") def test_open_position_sell_submits_order(mock_sleep: MagicMock) -> None: client, mock_trading = _make_client() @@ -113,7 +113,7 @@ def test_open_position_raises_value_error_for_negative_amount() -> None: client.open_position(ticker="AAPL", side=TradeSide.BUY, dollar_amount=-100.0) -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_open_position_raises_insufficient_buying_power_error() -> None: client, mock_trading = _make_client() mock_trading.submit_order.side_effect = _FakeAPIError("insufficient buying power") @@ -122,7 +122,7 @@ def test_open_position_raises_insufficient_buying_power_error() -> None: client.open_position(ticker="AAPL", side=TradeSide.BUY, dollar_amount=500.0) -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_open_position_raises_insufficient_buying_power_error_on_buying_power_keyword() -> ( # noqa: E501 None ): @@ -133,7 +133,7 @@ def test_open_position_raises_insufficient_buying_power_error_on_buying_power_ke client.open_position(ticker="AAPL", side=TradeSide.BUY, dollar_amount=500.0) -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_open_position_raises_asset_not_shortable_error_on_cannot_be_sold_short() -> ( None ): @@ -144,7 +144,7 @@ def test_open_position_raises_asset_not_shortable_error_on_cannot_be_sold_short( client.open_position(ticker="AAPL", side=TradeSide.SELL, dollar_amount=500.0) -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_open_position_raises_asset_not_shortable_error_on_not_shortable_keyword() -> ( None ): @@ -155,7 +155,7 @@ def test_open_position_raises_asset_not_shortable_error_on_not_shortable_keyword client.open_position(ticker="AAPL", side=TradeSide.SELL, dollar_amount=500.0) -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_open_position_reraises_other_api_errors() -> None: client, mock_trading = _make_client() mock_trading.submit_order.side_effect = _FakeAPIError("some unhandled error") @@ -164,7 +164,7 @@ def test_open_position_reraises_other_api_errors() -> None: client.open_position(ticker="AAPL", side=TradeSide.BUY, dollar_amount=500.0) -@patch("portfoliomanager.alpaca_client.time.sleep") +@patch("portfolio_manager.alpaca_client.time.sleep") def test_close_position_returns_true_on_success(mock_sleep: MagicMock) -> None: client, mock_trading = _make_client() @@ -175,7 +175,7 @@ def test_close_position_returns_true_on_success(mock_sleep: MagicMock) -> None: mock_sleep.assert_called_once_with(client.rate_limit_sleep) -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_close_position_returns_false_on_404_status_code() -> None: client, mock_trading = _make_client() mock_trading.close_position.side_effect = _FakeAPIError( @@ -187,7 +187,7 @@ def test_close_position_returns_false_on_404_status_code() -> None: assert result is False -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_close_position_returns_false_on_position_not_found_error_code() -> None: client, mock_trading = _make_client() mock_trading.close_position.side_effect = _FakeAPIError( @@ -199,7 +199,7 @@ def test_close_position_returns_false_on_position_not_found_error_code() -> None assert result is False -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_close_position_returns_false_on_position_not_found_in_message() -> None: client, mock_trading = _make_client() mock_trading.close_position.side_effect = _FakeAPIError("position not found") @@ -209,7 +209,7 @@ def test_close_position_returns_false_on_position_not_found_in_message() -> None assert result is False -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_close_position_returns_false_on_position_does_not_exist_in_message() -> None: client, mock_trading = _make_client() mock_trading.close_position.side_effect = _FakeAPIError("position does not exist") @@ -219,7 +219,7 @@ def test_close_position_returns_false_on_position_does_not_exist_in_message() -> assert result is False -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_close_position_falls_back_to_str_when_message_attribute_is_none() -> None: client, mock_trading = _make_client() error = _FakeAPIError("position not found") @@ -231,7 +231,7 @@ def test_close_position_falls_back_to_str_when_message_attribute_is_none() -> No assert result is False -@patch("portfoliomanager.alpaca_client.APIError", _FakeAPIError) +@patch("portfolio_manager.alpaca_client.APIError", _FakeAPIError) def test_close_position_reraises_other_api_errors() -> None: client, mock_trading = _make_client() mock_trading.close_position.side_effect = _FakeAPIError("some unhandled error") diff --git a/applications/portfoliomanager/tests/test_beta.py b/applications/portfolio_manager/tests/test_beta.py similarity index 98% rename from applications/portfoliomanager/tests/test_beta.py rename to applications/portfolio_manager/tests/test_beta.py index ff9d3a569..8fb2871dc 100644 --- a/applications/portfoliomanager/tests/test_beta.py +++ b/applications/portfolio_manager/tests/test_beta.py @@ -1,7 +1,7 @@ import numpy as np import polars as pl import pytest -from portfoliomanager.beta import compute_market_betas, compute_portfolio_beta +from portfolio_manager.beta import compute_market_betas, compute_portfolio_beta def _make_spy_prices( diff --git a/applications/portfoliomanager/tests/test_consolidation.py b/applications/portfolio_manager/tests/test_consolidation.py similarity index 99% rename from applications/portfoliomanager/tests/test_consolidation.py rename to applications/portfolio_manager/tests/test_consolidation.py index 82703546a..9ec470ce7 100644 --- a/applications/portfoliomanager/tests/test_consolidation.py +++ b/applications/portfolio_manager/tests/test_consolidation.py @@ -2,7 +2,7 @@ import polars as pl import pytest -from portfoliomanager.consolidation import consolidate_predictions +from portfolio_manager.consolidation import consolidate_predictions def _make_historical_prices(tickers: list[str], days: int = 25) -> pl.DataFrame: diff --git a/applications/portfoliomanager/tests/test_data_client.py b/applications/portfolio_manager/tests/test_data_client.py similarity index 84% rename from applications/portfoliomanager/tests/test_data_client.py rename to applications/portfolio_manager/tests/test_data_client.py index 19ce64302..40a58e0e1 100644 --- a/applications/portfoliomanager/tests/test_data_client.py +++ b/applications/portfolio_manager/tests/test_data_client.py @@ -5,12 +5,12 @@ import polars as pl import pytest import requests -from portfoliomanager.data_client import ( +from portfolio_manager.data_client import ( fetch_equity_details, fetch_historical_prices, fetch_spy_prices, ) -from portfoliomanager.exceptions import PriceDataUnavailableError +from portfolio_manager.exceptions import PriceDataUnavailableError def _make_parquet_bytes(dataframe: pl.DataFrame) -> bytes: @@ -36,7 +36,9 @@ def test_fetch_historical_prices_returns_expected_columns() -> None: mock_response.content = _make_parquet_bytes(raw) mock_response.raise_for_status.return_value = None - with patch("portfoliomanager.data_client.requests.get", return_value=mock_response): + with patch( + "portfolio_manager.data_client.requests.get", return_value=mock_response + ): result = fetch_historical_prices( "http://localhost", datetime(2024, 1, 2, tzinfo=UTC) ) @@ -57,7 +59,9 @@ def test_fetch_historical_prices_drops_null_close_prices() -> None: mock_response.content = _make_parquet_bytes(raw) mock_response.raise_for_status.return_value = None - with patch("portfoliomanager.data_client.requests.get", return_value=mock_response): + with patch( + "portfolio_manager.data_client.requests.get", return_value=mock_response + ): result = fetch_historical_prices( "http://localhost", datetime(2024, 1, 2, tzinfo=UTC) ) @@ -74,7 +78,7 @@ def test_fetch_historical_prices_sends_correct_query_params() -> None: mock_response.raise_for_status.return_value = None with patch( - "portfoliomanager.data_client.requests.get", return_value=mock_response + "portfolio_manager.data_client.requests.get", return_value=mock_response ) as mock_get: fetch_historical_prices( "http://datamanager:8080", reference_date, lookback_days=90 @@ -96,7 +100,7 @@ def test_fetch_historical_prices_raises_on_http_error() -> None: mock_response.raise_for_status.side_effect = requests.HTTPError("500 Server Error") with ( - patch("portfoliomanager.data_client.requests.get", return_value=mock_response), + patch("portfolio_manager.data_client.requests.get", return_value=mock_response), pytest.raises(PriceDataUnavailableError), ): fetch_historical_prices("http://localhost", datetime(2024, 1, 1, tzinfo=UTC)) @@ -105,7 +109,7 @@ def test_fetch_historical_prices_raises_on_http_error() -> None: def test_fetch_historical_prices_raises_on_network_error() -> None: with ( patch( - "portfoliomanager.data_client.requests.get", + "portfolio_manager.data_client.requests.get", side_effect=requests.RequestException("Connection refused"), ), pytest.raises(PriceDataUnavailableError), @@ -125,7 +129,9 @@ def test_fetch_equity_details_returns_expected_columns() -> None: mock_response.content = _make_csv_bytes(raw) mock_response.raise_for_status.return_value = None - with patch("portfoliomanager.data_client.requests.get", return_value=mock_response): + with patch( + "portfolio_manager.data_client.requests.get", return_value=mock_response + ): result = fetch_equity_details("http://localhost") assert result.columns == ["ticker", "sector"] @@ -137,7 +143,7 @@ def test_fetch_equity_details_raises_on_http_error() -> None: mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found") with ( - patch("portfoliomanager.data_client.requests.get", return_value=mock_response), + patch("portfolio_manager.data_client.requests.get", return_value=mock_response), pytest.raises(PriceDataUnavailableError), ): fetch_equity_details("http://localhost") @@ -146,7 +152,7 @@ def test_fetch_equity_details_raises_on_http_error() -> None: def test_fetch_equity_details_raises_on_network_error() -> None: with ( patch( - "portfoliomanager.data_client.requests.get", + "portfolio_manager.data_client.requests.get", side_effect=requests.RequestException("Timeout"), ), pytest.raises(PriceDataUnavailableError), @@ -167,7 +173,9 @@ def test_fetch_spy_prices_returns_expected_columns() -> None: mock_response.content = _make_parquet_bytes(raw) mock_response.raise_for_status.return_value = None - with patch("portfoliomanager.data_client.requests.get", return_value=mock_response): + with patch( + "portfolio_manager.data_client.requests.get", return_value=mock_response + ): result = fetch_spy_prices("http://localhost", datetime(2024, 1, 3, tzinfo=UTC)) assert result.columns == ["ticker", "timestamp", "close_price"] @@ -186,7 +194,9 @@ def test_fetch_spy_prices_drops_null_close_prices() -> None: mock_response.content = _make_parquet_bytes(raw) mock_response.raise_for_status.return_value = None - with patch("portfoliomanager.data_client.requests.get", return_value=mock_response): + with patch( + "portfolio_manager.data_client.requests.get", return_value=mock_response + ): result = fetch_spy_prices("http://localhost", datetime(2024, 1, 3, tzinfo=UTC)) assert result.height == 1 @@ -200,7 +210,7 @@ def test_fetch_spy_prices_sends_correct_query_params() -> None: mock_response.raise_for_status.return_value = None with patch( - "portfoliomanager.data_client.requests.get", return_value=mock_response + "portfolio_manager.data_client.requests.get", return_value=mock_response ) as mock_get: fetch_spy_prices("http://datamanager:8080", reference_date, lookback_days=90) @@ -221,7 +231,7 @@ def test_fetch_spy_prices_raises_on_http_error() -> None: mock_response.raise_for_status.side_effect = requests.HTTPError("500 Server Error") with ( - patch("portfoliomanager.data_client.requests.get", return_value=mock_response), + patch("portfolio_manager.data_client.requests.get", return_value=mock_response), pytest.raises(PriceDataUnavailableError), ): fetch_spy_prices("http://localhost", datetime(2024, 1, 1, tzinfo=UTC)) @@ -230,7 +240,7 @@ def test_fetch_spy_prices_raises_on_http_error() -> None: def test_fetch_spy_prices_raises_on_network_error() -> None: with ( patch( - "portfoliomanager.data_client.requests.get", + "portfolio_manager.data_client.requests.get", side_effect=requests.RequestException("Connection refused"), ), pytest.raises(PriceDataUnavailableError), diff --git a/applications/portfoliomanager/tests/test_portfolio_schema.py b/applications/portfolio_manager/tests/test_portfolio_schema.py similarity index 99% rename from applications/portfoliomanager/tests/test_portfolio_schema.py rename to applications/portfolio_manager/tests/test_portfolio_schema.py index e70f4e0ab..f205f5454 100644 --- a/applications/portfoliomanager/tests/test_portfolio_schema.py +++ b/applications/portfolio_manager/tests/test_portfolio_schema.py @@ -5,7 +5,7 @@ import pytest from pandera.errors import SchemaError from pandera.polars import PolarsData -from portfoliomanager.portfolio_schema import ( +from portfolio_manager.portfolio_schema import ( check_pair_tickers_different, check_position_side_counts, check_position_side_sums, diff --git a/applications/portfoliomanager/tests/test_portfolio_server.py b/applications/portfolio_manager/tests/test_portfolio_server.py similarity index 92% rename from applications/portfoliomanager/tests/test_portfolio_server.py rename to applications/portfolio_manager/tests/test_portfolio_server.py index d93abdb23..cc9f26177 100644 --- a/applications/portfoliomanager/tests/test_portfolio_server.py +++ b/applications/portfolio_manager/tests/test_portfolio_server.py @@ -3,7 +3,7 @@ import polars as pl import pytest -from portfoliomanager.server import ( +from portfolio_manager.server import ( _PRIOR_PORTFOLIO_SCHEMA, evaluate_prior_pairs, get_positions, @@ -80,7 +80,7 @@ def test_evaluate_prior_pairs_holds_pair_in_intermediate_zone() -> None: ) historical_prices = _make_historical_prices(["AAPL", "MSFT"]) with patch( - "portfoliomanager.server.compute_spread_zscore", return_value=(2.0, 1.0) + "portfolio_manager.server.compute_spread_zscore", return_value=(2.0, 1.0) ): result = evaluate_prior_pairs(prior, historical_prices) assert "AAPL" in result @@ -93,7 +93,7 @@ def test_evaluate_prior_pairs_holds_pair_at_lower_bound_of_hold_zone() -> None: ) historical_prices = _make_historical_prices(["AAPL", "MSFT"]) with patch( - "portfoliomanager.server.compute_spread_zscore", return_value=(0.5, 1.0) + "portfolio_manager.server.compute_spread_zscore", return_value=(0.5, 1.0) ): result = evaluate_prior_pairs(prior, historical_prices) assert "AAPL" in result @@ -106,7 +106,7 @@ def test_evaluate_prior_pairs_does_not_hold_converged_pair() -> None: ) historical_prices = _make_historical_prices(["AAPL", "MSFT"]) with patch( - "portfoliomanager.server.compute_spread_zscore", return_value=(0.2, 1.0) + "portfolio_manager.server.compute_spread_zscore", return_value=(0.2, 1.0) ): result = evaluate_prior_pairs(prior, historical_prices) assert "AAPL" not in result @@ -119,7 +119,7 @@ def test_evaluate_prior_pairs_does_not_hold_stop_loss_pair() -> None: ) historical_prices = _make_historical_prices(["AAPL", "MSFT"]) with patch( - "portfoliomanager.server.compute_spread_zscore", return_value=(5.0, 1.0) + "portfolio_manager.server.compute_spread_zscore", return_value=(5.0, 1.0) ): result = evaluate_prior_pairs(prior, historical_prices) assert "AAPL" not in result @@ -132,7 +132,7 @@ def test_evaluate_prior_pairs_does_not_hold_pair_at_stop_loss_boundary() -> None ) historical_prices = _make_historical_prices(["AAPL", "MSFT"]) with patch( - "portfoliomanager.server.compute_spread_zscore", return_value=(4.0, 1.0) + "portfolio_manager.server.compute_spread_zscore", return_value=(4.0, 1.0) ): result = evaluate_prior_pairs(prior, historical_prices) assert "AAPL" not in result @@ -145,7 +145,7 @@ def test_evaluate_prior_pairs_handles_negative_z_score_in_hold_zone() -> None: ) historical_prices = _make_historical_prices(["AAPL", "MSFT"]) with patch( - "portfoliomanager.server.compute_spread_zscore", return_value=(-2.0, 1.0) + "portfolio_manager.server.compute_spread_zscore", return_value=(-2.0, 1.0) ): result = evaluate_prior_pairs(prior, historical_prices) assert "AAPL" in result @@ -220,7 +220,7 @@ def test_evaluate_prior_pairs_skips_pair_with_nan_z_score() -> None: ) historical_prices = _make_historical_prices(["AAPL", "MSFT"]) with patch( - "portfoliomanager.server.compute_spread_zscore", + "portfolio_manager.server.compute_spread_zscore", return_value=(float("nan"), 1.0), ): result = evaluate_prior_pairs(prior, historical_prices) @@ -239,7 +239,7 @@ def test_evaluate_prior_pairs_holds_multiple_pairs_independently() -> None: # pair_ids are sorted: "AAPL-MSFT" < "GOOGL-AMZN" # first call → AAPL-MSFT (z=2.0, held), second → GOOGL-AMZN (z=0.2, closed) with patch( - "portfoliomanager.server.compute_spread_zscore", + "portfolio_manager.server.compute_spread_zscore", side_effect=[(2.0, 1.0), (0.2, 1.0)], ): result = evaluate_prior_pairs(prior, historical_prices) @@ -254,7 +254,7 @@ def test_get_prior_portfolio_returns_empty_dataframe_on_empty_array_response() - mock_response = MagicMock() mock_response.status_code = 200 mock_response.text = "[]" - with patch("portfoliomanager.server.requests.get", return_value=mock_response): + with patch("portfolio_manager.server.requests.get", return_value=mock_response): result = get_prior_portfolio() assert result.is_empty() assert "pair_id" in result.columns @@ -275,7 +275,7 @@ def test_get_prior_portfolio_returns_dataframe_with_pair_id_on_success() -> None mock_response.status_code = 200 mock_response.text = json.dumps(data) mock_response.json.return_value = data - with patch("portfoliomanager.server.requests.get", return_value=mock_response): + with patch("portfolio_manager.server.requests.get", return_value=mock_response): result = get_prior_portfolio() assert result.height == 1 assert "pair_id" in result.columns @@ -285,7 +285,7 @@ def test_get_prior_portfolio_returns_dataframe_with_pair_id_on_success() -> None def test_get_prior_portfolio_returns_empty_dataframe_on_error_response() -> None: mock_response = MagicMock() mock_response.status_code = 500 - with patch("portfoliomanager.server.requests.get", return_value=mock_response): + with patch("portfolio_manager.server.requests.get", return_value=mock_response): result = get_prior_portfolio() assert result.is_empty() assert "pair_id" in result.columns @@ -295,7 +295,7 @@ def test_get_prior_portfolio_returns_empty_dataframe_on_whitespace_response() -> mock_response = MagicMock() mock_response.status_code = 200 mock_response.text = " " - with patch("portfoliomanager.server.requests.get", return_value=mock_response): + with patch("portfolio_manager.server.requests.get", return_value=mock_response): result = get_prior_portfolio() assert result.is_empty() diff --git a/applications/portfoliomanager/tests/test_regime.py b/applications/portfolio_manager/tests/test_regime.py similarity index 99% rename from applications/portfoliomanager/tests/test_regime.py rename to applications/portfolio_manager/tests/test_regime.py index 5f178558b..52ccff687 100644 --- a/applications/portfoliomanager/tests/test_regime.py +++ b/applications/portfolio_manager/tests/test_regime.py @@ -1,6 +1,6 @@ import numpy as np import polars as pl -from portfoliomanager.regime import ( +from portfolio_manager.regime import ( REGIME_AUTOCORRELATION_THRESHOLD, REGIME_VOLATILITY_THRESHOLD, REGIME_WINDOW_DAYS, diff --git a/applications/portfoliomanager/tests/test_report.py b/applications/portfolio_manager/tests/test_report.py similarity index 99% rename from applications/portfoliomanager/tests/test_report.py rename to applications/portfolio_manager/tests/test_report.py index 5a8b534a8..d2277de85 100644 --- a/applications/portfoliomanager/tests/test_report.py +++ b/applications/portfolio_manager/tests/test_report.py @@ -1,5 +1,5 @@ import polars as pl -from portfoliomanager.report import ( +from portfolio_manager.report import ( _SEPARATOR, _W_TICKER, format_beta_report, diff --git a/applications/portfoliomanager/tests/test_risk_management.py b/applications/portfolio_manager/tests/test_risk_management.py similarity index 96% rename from applications/portfoliomanager/tests/test_risk_management.py rename to applications/portfolio_manager/tests/test_risk_management.py index 248bb3d7b..9bde36065 100644 --- a/applications/portfoliomanager/tests/test_risk_management.py +++ b/applications/portfolio_manager/tests/test_risk_management.py @@ -2,10 +2,10 @@ import polars as pl import pytest -from portfoliomanager.beta import compute_portfolio_beta -from portfoliomanager.exceptions import InsufficientPairsError -from portfoliomanager.portfolio_schema import portfolio_schema -from portfoliomanager.risk_management import ( +from portfolio_manager.beta import compute_portfolio_beta +from portfolio_manager.exceptions import InsufficientPairsError +from portfolio_manager.portfolio_schema import portfolio_schema +from portfolio_manager.risk_management import ( REQUIRED_PAIRS, size_pairs_with_volatility_parity, ) diff --git a/applications/portfoliomanager/tests/test_statistical_arbitrage.py b/applications/portfolio_manager/tests/test_statistical_arbitrage.py similarity index 99% rename from applications/portfoliomanager/tests/test_statistical_arbitrage.py rename to applications/portfolio_manager/tests/test_statistical_arbitrage.py index babc9f6bd..6f54d7723 100644 --- a/applications/portfoliomanager/tests/test_statistical_arbitrage.py +++ b/applications/portfolio_manager/tests/test_statistical_arbitrage.py @@ -1,6 +1,6 @@ import numpy as np import polars as pl -from portfoliomanager.statistical_arbitrage import ( +from portfolio_manager.statistical_arbitrage import ( _PAIRS_OUTPUT_SCHEMA, CONFIDENCE_THRESHOLD, CORRELATION_WINDOW_DAYS, diff --git a/libraries/python/pyproject.toml b/libraries/python/pyproject.toml index 14f5b6bef..6736fc91d 100644 --- a/libraries/python/pyproject.toml +++ b/libraries/python/pyproject.toml @@ -3,7 +3,7 @@ name = "internal" version = "0.0.1" description = "Shared Python resources" requires-python = "==3.12.10" -dependencies = ["polars>=1.29.0", "pandera[polars]>=0.26.0"] +dependencies = ["polars>=1.29.0", "pandera[polars]>=0.26.0", "structlog>=25.5.0"] [tool.uv] package = true diff --git a/applications/equitypricemodel/src/equitypricemodel/combine_data.py b/libraries/python/src/internal/combine_data.py similarity index 84% rename from applications/equitypricemodel/src/equitypricemodel/combine_data.py rename to libraries/python/src/internal/combine_data.py index 36135183c..df3ad766c 100644 --- a/applications/equitypricemodel/src/equitypricemodel/combine_data.py +++ b/libraries/python/src/internal/combine_data.py @@ -1,12 +1,10 @@ -import sys - import polars as pl import structlog from .equity_details_schema import equity_details_schema -def combine_data( +def combine_equity_bars_and_details( equity_details_csv_path: str, equity_bars_csv_path: str, output_csv_path: str, @@ -76,15 +74,3 @@ def combine_data( error=str(e), ) raise - - -if __name__ == "__main__": - logger = structlog.get_logger() - - if len(sys.argv) != 4: # noqa: PLR2004 - logger.error( - "Requires equity details CSV, equity bars CSV, and output CSV paths as arguments", # noqa: E501 - ) - sys.exit(1) - - combine_data(sys.argv[1], sys.argv[2], sys.argv[3]) diff --git a/applications/equitypricemodel/src/equitypricemodel/equity_details_schema.py b/libraries/python/src/internal/equity_details_schema.py similarity index 100% rename from applications/equitypricemodel/src/equitypricemodel/equity_details_schema.py rename to libraries/python/src/internal/equity_details_schema.py diff --git a/applications/equitypricemodel/tests/test_equity_details_schema.py b/libraries/python/tests/test_equity_details_schema.py similarity index 98% rename from applications/equitypricemodel/tests/test_equity_details_schema.py rename to libraries/python/tests/test_equity_details_schema.py index 994f015d5..d4bf4f2bc 100644 --- a/applications/equitypricemodel/tests/test_equity_details_schema.py +++ b/libraries/python/tests/test_equity_details_schema.py @@ -1,6 +1,6 @@ import polars as pl import pytest -from equitypricemodel.equity_details_schema import equity_details_schema +from internal.equity_details_schema import equity_details_schema from pandera.errors import SchemaError diff --git a/tools/src/tools/flows/__init__.py b/models/__init__.py similarity index 100% rename from tools/src/tools/flows/__init__.py rename to models/__init__.py diff --git a/models/tide/Dockerfile b/models/tide/Dockerfile new file mode 100644 index 000000000..950e6670c --- /dev/null +++ b/models/tide/Dockerfile @@ -0,0 +1,43 @@ +FROM python:3.12.10-slim AS builder + +COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv + +WORKDIR /app + +COPY pyproject.toml uv.lock ./ + +COPY models/tide/ models/tide/ + +COPY libraries/python/ libraries/python/ + +COPY tools/ tools/ + +RUN uv sync --no-dev --package tide + +FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS trainer + +ENV DEBIAN_FRONTEND=noninteractive +ENV TZ=UTC + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + software-properties-common && \ + add-apt-repository -y ppa:deadsnakes/ppa && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + python3.12 python3.12-venv python3.12-dev \ + build-essential clang curl && \ + rm -rf /var/lib/apt/lists/* && \ + ln -sf /usr/bin/python3.12 /usr/bin/python + +ENV CUDA=1 + +WORKDIR /app + +COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv + +COPY --from=builder /app /app + +ENV PYTHONPATH=/app/models/tide/src + +ENTRYPOINT ["uv", "run", "--package", "tide", "python", "-m", "tide.workflow"] diff --git a/models/tide/__init__.py b/models/tide/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/models/tide/pyproject.toml b/models/tide/pyproject.toml new file mode 100644 index 000000000..38342e5d5 --- /dev/null +++ b/models/tide/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name = "tide" +version = "0.0.1" +description = "TiDE model training workflow" +requires-python = "==3.12.10" +dependencies = [ + "tools>=0.0.1", + "boto3>=1.40.74", + "prefect>=3.0.0,<4.0.0", + "polars>=1.29.0", + "structlog>=25.5.0", + "tinygrad>=0.10.3", + "numpy>=1.26.4", + "pandera[polars]>=0.26.0", + "requests>=2.32.5", +] + +[dependency-groups] +dev = ["boto3-stubs[s3]>=1.38.0"] + +[tool.uv.sources] +tools = { workspace = true } + +[tool.uv] +package = true +src = ["src"] diff --git a/models/tide/src/tide/__init__.py b/models/tide/src/tide/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tools/src/tools/deploy_training_flow.py b/models/tide/src/tide/deploy.py similarity index 87% rename from tools/src/tools/deploy_training_flow.py rename to models/tide/src/tide/deploy.py index d3dbe7551..f147b1c14 100644 --- a/tools/src/tools/deploy_training_flow.py +++ b/models/tide/src/tide/deploy.py @@ -4,7 +4,7 @@ import structlog from prefect.flows import EntrypointType -from tools.flows.training_flow import training_pipeline +from tide.workflow import training_pipeline logger = structlog.get_logger() @@ -49,13 +49,13 @@ def deploy_training_flow( artifacts_bucket = os.getenv("AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME", "") try: - lookback_days = int(os.getenv("LOOKBACK_DAYS", "365")) + lookback_days = int(os.getenv("FUND_LOOKBACK_DAYS", "365")) except ValueError: - logger.exception("LOOKBACK_DAYS must be a valid integer") + logger.exception("FUND_LOOKBACK_DAYS must be a valid integer") sys.exit(1) if lookback_days <= 0: - logger.error("LOOKBACK_DAYS must be positive", lookback_days=lookback_days) + logger.error("FUND_LOOKBACK_DAYS must be positive", lookback_days=lookback_days) sys.exit(1) required_vars = { diff --git a/tools/src/tools/flows/notifications.py b/models/tide/src/tide/notifications.py similarity index 94% rename from tools/src/tools/flows/notifications.py rename to models/tide/src/tide/notifications.py index eda405af2..dddd673b4 100644 --- a/tools/src/tools/flows/notifications.py +++ b/models/tide/src/tide/notifications.py @@ -10,9 +10,9 @@ def send_training_notification(flow: Flow, flow_run: FlowRun, state: State) -> None: """Send email notification via SES on training pipeline completion or failure.""" - sender_email = os.getenv("TRAINING_NOTIFICATION_SENDER_EMAIL", "").strip() + sender_email = os.getenv("FUND_TRAINING_NOTIFICATION_SENDER_EMAIL", "").strip() recipient_emails_raw = os.getenv( - "TRAINING_NOTIFICATION_RECIPIENT_EMAILS", "" + "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS", "" ).strip() if not sender_email or not recipient_emails_raw: diff --git a/tools/src/tools/run_training_job.py b/models/tide/src/tide/run.py similarity index 87% rename from tools/src/tools/run_training_job.py rename to models/tide/src/tide/run.py index 20f9c6c88..69f660b0e 100644 --- a/tools/src/tools/run_training_job.py +++ b/models/tide/src/tide/run.py @@ -3,7 +3,7 @@ import structlog -from tools.flows.training_flow import training_pipeline +from tide.workflow import training_pipeline logger = structlog.get_logger() @@ -56,13 +56,13 @@ def run_training_job( sys.exit(1) try: - lookback_days = int(os.getenv("LOOKBACK_DAYS", "365")) + lookback_days = int(os.getenv("FUND_LOOKBACK_DAYS", "365")) except ValueError: - logger.exception("LOOKBACK_DAYS must be a valid integer") + logger.exception("FUND_LOOKBACK_DAYS must be a valid integer") sys.exit(1) if lookback_days <= 0: - logger.error("LOOKBACK_DAYS must be positive", lookback_days=lookback_days) + logger.error("FUND_LOOKBACK_DAYS must be positive", lookback_days=lookback_days) sys.exit(1) try: diff --git a/tools/src/tools/prepare_training_data.py b/models/tide/src/tide/tasks.py similarity index 83% rename from tools/src/tools/prepare_training_data.py rename to models/tide/src/tide/tasks.py index 4ea0b4c85..51514aeeb 100644 --- a/tools/src/tools/prepare_training_data.py +++ b/models/tide/src/tide/tasks.py @@ -1,8 +1,6 @@ import io -import os -import sys -from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING, cast +from datetime import datetime, timedelta +from typing import TYPE_CHECKING import boto3 import polars as pl @@ -236,33 +234,3 @@ def prepare_training_data( data=consolidated, output_key=output_key, ) - - -if __name__ == "__main__": - data_bucket: str | None = os.getenv("AWS_S3_DATA_BUCKET_NAME") - model_artifacts_bucket: str | None = os.getenv("AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME") - lookback_days = int(os.getenv("LOOKBACK_DAYS", "365")) - - if data_bucket is None: - logger.error("AWS_S3_DATA_BUCKET_NAME environment variable not set") - sys.exit(1) - - if model_artifacts_bucket is None: - logger.error("AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME environment variable not set") - sys.exit(1) - - end_date = datetime.now(tz=UTC).replace(hour=0, minute=0, second=0, microsecond=0) - start_date = end_date - timedelta(days=lookback_days) - - try: - output_uri = prepare_training_data( - data_bucket_name=cast("str", data_bucket), - model_artifacts_bucket_name=cast("str", model_artifacts_bucket), - start_date=start_date, - end_date=end_date, - ) - logger.info("Training data preparation complete", output_uri=output_uri) - - except Exception as e: - logger.exception("Failed to prepare training data", error=str(e)) - sys.exit(1) diff --git a/applications/equitypricemodel/src/equitypricemodel/tide_data.py b/models/tide/src/tide/tide_data.py similarity index 100% rename from applications/equitypricemodel/src/equitypricemodel/tide_data.py rename to models/tide/src/tide/tide_data.py diff --git a/applications/equitypricemodel/src/equitypricemodel/tide_model.py b/models/tide/src/tide/tide_model.py similarity index 100% rename from applications/equitypricemodel/src/equitypricemodel/tide_model.py rename to models/tide/src/tide/tide_model.py diff --git a/applications/equitypricemodel/src/equitypricemodel/trainer.py b/models/tide/src/tide/trainer.py similarity index 71% rename from applications/equitypricemodel/src/equitypricemodel/trainer.py rename to models/tide/src/tide/trainer.py index 569fd1d6f..44e22b510 100644 --- a/applications/equitypricemodel/src/equitypricemodel/trainer.py +++ b/models/tide/src/tide/trainer.py @@ -1,11 +1,10 @@ -import os from typing import cast import polars as pl import structlog -from equitypricemodel.tide_data import Data -from equitypricemodel.tide_model import Model -from tinygrad import Device + +from tide.tide_data import Data +from tide.tide_model import Model logger = structlog.get_logger() @@ -131,56 +130,3 @@ def train_model( ) return tide_model, tide_data - - -if __name__ == "__main__": - # Configure structlog for CloudWatch-friendly output - structlog.configure( - processors=[ - structlog.stdlib.add_log_level, - structlog.processors.TimeStamper(fmt="iso"), - structlog.processors.JSONRenderer(), - ], - wrapper_class=structlog.BoundLogger, - context_class=dict, - logger_factory=structlog.PrintLoggerFactory(), - cache_logger_on_first_use=True, - ) - - logger.info("Trainer started", device=Device.DEFAULT) - - training_data_input_path = os.environ.get( - "TRAINING_DATA_PATH", - os.path.join( # noqa: PTH118 - "/app/training-data", - "filtered_tide_training_data.parquet", - ), - ) - - model_output_path = os.environ.get("MODEL_OUTPUT_PATH", "/app/model-artifacts") - - logger.info( - "Paths configured", - training_data_path=training_data_input_path, - model_output_path=model_output_path, - ) - - logger.info("Loading training data") - training_data = pl.read_parquet(training_data_input_path) - logger.info( - "Training data loaded", - rows=training_data.height, - columns=training_data.width, - ) - - tide_model, tide_data = train_model( - training_data, checkpoint_directory=model_output_path - ) - - logger.info("Saving model") - tide_model.save(directory_path=model_output_path) - - logger.info("Saving data processor") - tide_data.save(directory_path=model_output_path) - - logger.info("Trainer complete") diff --git a/tools/src/tools/flows/training_flow.py b/models/tide/src/tide/workflow.py similarity index 93% rename from tools/src/tools/flows/training_flow.py rename to models/tide/src/tide/workflow.py index 58fdd68c7..716baeb96 100644 --- a/tools/src/tools/flows/training_flow.py +++ b/models/tide/src/tide/workflow.py @@ -10,12 +10,12 @@ import polars as pl import structlog from prefect import flow, task - -from tools.flows.notifications import send_training_notification -from tools.prepare_training_data import prepare_training_data from tools.sync_equity_bars_data import sync_equity_bars_data from tools.sync_equity_details_data import sync_equity_details_data +from tide.notifications import send_training_notification +from tide.tasks import prepare_training_data + logger = structlog.get_logger() @@ -103,7 +103,7 @@ def train_tide_model( ) -> str: """Download training data from S3, train model, upload artifact to S3.""" # Defer import to avoid importing tinygrad at module level (heavy GPU dependency) - from equitypricemodel.trainer import train_model # noqa: PLC0415 + from tide.trainer import train_model # noqa: PLC0415 resolved_training_data_key = training_data_key bucket_prefix = f"s3://{artifacts_bucket}/" @@ -132,7 +132,7 @@ def train_tide_model( ) timestamp = datetime.now(tz=UTC).strftime("%Y-%m-%d-%H-%M-%S-%f")[:-3] - artifact_folder = f"artifacts/equitypricemodel-trainer-{timestamp}" + artifact_folder = f"artifacts/tide/{timestamp}" artifact_key = f"{artifact_folder}/output/model.tar.gz" with tempfile.TemporaryDirectory() as tmpdir: @@ -205,13 +205,13 @@ def training_pipeline( artifacts_bucket = os.getenv("AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME", "") try: - lookback_days = int(os.getenv("LOOKBACK_DAYS", "365")) + lookback_days = int(os.getenv("FUND_LOOKBACK_DAYS", "365")) except ValueError: - logger.exception("LOOKBACK_DAYS must be a valid integer") + logger.exception("FUND_LOOKBACK_DAYS must be a valid integer") sys.exit(1) if lookback_days <= 0: - logger.error("LOOKBACK_DAYS must be positive", lookback_days=lookback_days) + logger.error("FUND_LOOKBACK_DAYS must be positive", lookback_days=lookback_days) sys.exit(1) required_vars = { diff --git a/models/tide/tests/__init__.py b/models/tide/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/models/tide/tests/conftest.py b/models/tide/tests/conftest.py new file mode 100644 index 000000000..f143665b2 --- /dev/null +++ b/models/tide/tests/conftest.py @@ -0,0 +1,51 @@ +from collections.abc import Callable +from datetime import UTC, date, datetime, timedelta + +import polars as pl +import pytest + +SATURDAY_WEEKDAY = 5 + + +@pytest.fixture +def make_raw_data() -> Callable[..., pl.DataFrame]: + def _make_raw_data( + tickers: list[str] | None = None, + days: int = 60, + start_date: date | None = None, + ) -> pl.DataFrame: + tickers = tickers or ["AAPL", "GOOG"] + start = start_date or date(2024, 1, 2) + rows = [] + for ticker in tickers: + for day_offset in range(days): + current_date = start + timedelta(days=day_offset) + if current_date.weekday() >= SATURDAY_WEEKDAY: + continue + timestamp = int( + datetime( + current_date.year, + current_date.month, + current_date.day, + tzinfo=UTC, + ).timestamp() + * 1000 + ) + close = 100.0 + day_offset * 0.5 + rows.append( + { + "ticker": ticker, + "timestamp": timestamp, + "open_price": close - 1.0, + "high_price": close + 1.0, + "low_price": close - 2.0, + "close_price": close, + "volume": 1_000_000, + "volume_weighted_average_price": close + 0.1, + "sector": "Technology", + "industry": "Software", + } + ) + return pl.DataFrame(rows) + + return _make_raw_data diff --git a/tools/tests/test_deploy_training_flow.py b/models/tide/tests/test_deploy.py similarity index 88% rename from tools/tests/test_deploy_training_flow.py rename to models/tide/tests/test_deploy.py index 2306f0172..7da5c9870 100644 --- a/tools/tests/test_deploy_training_flow.py +++ b/models/tide/tests/test_deploy.py @@ -1,12 +1,12 @@ from unittest.mock import MagicMock, patch from prefect.flows import EntrypointType -from tools.deploy_training_flow import deploy_training_flow +from tide.deploy import deploy_training_flow LOOKBACK_DAYS = 30 -@patch("tools.deploy_training_flow.training_pipeline") +@patch("tide.deploy.training_pipeline") def test_deploy_training_flow_calls_deploy(mock_pipeline: MagicMock) -> None: mock_deploy = MagicMock() mock_pipeline.deploy = mock_deploy @@ -27,7 +27,7 @@ def test_deploy_training_flow_calls_deploy(mock_pipeline: MagicMock) -> None: assert call_kwargs["parameters"]["lookback_days"] == LOOKBACK_DAYS -@patch("tools.deploy_training_flow.training_pipeline") +@patch("tide.deploy.training_pipeline") def test_deploy_training_flow_sets_module_path_entrypoint( mock_pipeline: MagicMock, ) -> None: diff --git a/tools/tests/test_notifications.py b/models/tide/tests/test_notifications.py similarity index 76% rename from tools/tests/test_notifications.py rename to models/tide/tests/test_notifications.py index 7ed347072..4610418e4 100644 --- a/tools/tests/test_notifications.py +++ b/models/tide/tests/test_notifications.py @@ -5,7 +5,7 @@ import pendulum from prefect.client.schemas.objects import Flow, FlowRun, State, StateType from pydantic_extra_types.pendulum_dt import DateTime -from tools.flows.notifications import send_training_notification +from tide.notifications import send_training_notification def _make_flow() -> Flow: @@ -42,11 +42,11 @@ def test_send_training_notification_on_completion() -> None: patch.dict( "os.environ", { - "TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", - "TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "recipient@example.com", + "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", + "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "recipient@example.com", }, ), - patch("tools.flows.notifications.boto3") as mock_boto3, + patch("tide.notifications.boto3") as mock_boto3, ): mock_boto3.client.return_value = mock_ses send_training_notification(flow, flow_run, state) @@ -77,11 +77,11 @@ def test_send_training_notification_reports_zero_second_duration() -> None: patch.dict( "os.environ", { - "TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", - "TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "recipient@example.com", + "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", + "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "recipient@example.com", }, ), - patch("tools.flows.notifications.boto3") as mock_boto3, + patch("tide.notifications.boto3") as mock_boto3, ): mock_boto3.client.return_value = mock_ses send_training_notification(flow, flow_run, state) @@ -105,11 +105,13 @@ def test_send_training_notification_on_failure() -> None: patch.dict( "os.environ", { - "TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", - "TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "a@example.com,b@example.com", + "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", + "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS": ( + "a@example.com,b@example.com" + ), }, ), - patch("tools.flows.notifications.boto3") as mock_boto3, + patch("tide.notifications.boto3") as mock_boto3, ): mock_boto3.client.return_value = mock_ses send_training_notification(flow, flow_run, state) @@ -132,11 +134,11 @@ def test_send_training_notification_skips_when_not_configured() -> None: patch.dict( "os.environ", { - "TRAINING_NOTIFICATION_SENDER_EMAIL": "", - "TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "", + "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL": "", + "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "", }, ), - patch("tools.flows.notifications.boto3") as mock_boto3, + patch("tide.notifications.boto3") as mock_boto3, ): send_training_notification(flow, flow_run, state) @@ -152,11 +154,11 @@ def test_send_training_notification_skips_when_recipients_parse_empty() -> None: patch.dict( "os.environ", { - "TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", - "TRAINING_NOTIFICATION_RECIPIENT_EMAILS": " , , ", + "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", + "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS": " , , ", }, ), - patch("tools.flows.notifications.boto3") as mock_boto3, + patch("tide.notifications.boto3") as mock_boto3, ): send_training_notification(flow, flow_run, state) @@ -175,12 +177,12 @@ def test_send_training_notification_handles_ses_error() -> None: patch.dict( "os.environ", { - "TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", - "TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "recipient@example.com", + "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL": "sender@example.com", + "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS": "recipient@example.com", }, ), - patch("tools.flows.notifications.boto3") as mock_boto3, - patch("tools.flows.notifications.logger") as mock_logger, + patch("tide.notifications.boto3") as mock_boto3, + patch("tide.notifications.logger") as mock_logger, ): mock_boto3.client.return_value = mock_ses send_training_notification(flow, flow_run, state) diff --git a/tools/tests/test_run_training_job.py b/models/tide/tests/test_run.py similarity index 85% rename from tools/tests/test_run_training_job.py rename to models/tide/tests/test_run.py index fdb12a4d2..2a285dccb 100644 --- a/tools/tests/test_run_training_job.py +++ b/models/tide/tests/test_run.py @@ -1,12 +1,12 @@ from unittest.mock import patch import pytest -from tools.run_training_job import run_training_job +from tide.run import run_training_job def test_run_training_job_calls_training_pipeline() -> None: with patch( - "tools.run_training_job.training_pipeline", + "tide.run.training_pipeline", return_value="s3://bucket/artifacts/model.tar.gz", ) as mock_pipeline: result = run_training_job( @@ -26,9 +26,11 @@ def test_run_training_job_calls_training_pipeline() -> None: def test_run_training_job_returns_artifact_path() -> None: - expected_path = "s3://my-bucket/artifacts/equitypricemodel-trainer-2024-01-01/output/model.tar.gz" + expected_path = ( + "s3://my-bucket/artifacts/tide-trainer-2024-01-01/output/model.tar.gz" + ) with patch( - "tools.run_training_job.training_pipeline", + "tide.run.training_pipeline", return_value=expected_path, ): result = run_training_job( @@ -43,7 +45,7 @@ def test_run_training_job_returns_artifact_path() -> None: def test_run_training_job_propagates_errors() -> None: with ( patch( - "tools.run_training_job.training_pipeline", + "tide.run.training_pipeline", side_effect=RuntimeError("Training failed"), ), pytest.raises(RuntimeError, match="Training failed"), diff --git a/tools/tests/test_prepare_training_data.py b/models/tide/tests/test_tasks.py similarity index 97% rename from tools/tests/test_prepare_training_data.py rename to models/tide/tests/test_tasks.py index dbbd67feb..9a9f7b828 100644 --- a/tools/tests/test_prepare_training_data.py +++ b/models/tide/tests/test_tasks.py @@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch import polars as pl -from tools.prepare_training_data import ( +from tide.tasks import ( MINIMUM_CLOSE_PRICE, MINIMUM_VOLUME, consolidate_data, @@ -168,7 +168,7 @@ def test_prepare_training_data_returns_s3_uri() -> None: {"Body": mock_body_categories}, ] - with patch("tools.prepare_training_data.boto3.client", return_value=mock_s3_client): + with patch("tide.tasks.boto3.client", return_value=mock_s3_client): result = prepare_training_data( data_bucket_name="test-data-bucket", model_artifacts_bucket_name="test-artifacts-bucket", diff --git a/applications/equitypricemodel/tests/test_tide_data.py b/models/tide/tests/test_tide_data.py similarity index 99% rename from applications/equitypricemodel/tests/test_tide_data.py rename to models/tide/tests/test_tide_data.py index 442bc21e2..9411fbee5 100644 --- a/applications/equitypricemodel/tests/test_tide_data.py +++ b/models/tide/tests/test_tide_data.py @@ -6,7 +6,7 @@ import polars as pl import pytest -from equitypricemodel.tide_data import ( +from tide.tide_data import ( CleanData, Data, EngineerFeatures, diff --git a/applications/equitypricemodel/tests/test_tide_model.py b/models/tide/tests/test_tide_model.py similarity index 99% rename from applications/equitypricemodel/tests/test_tide_model.py rename to models/tide/tests/test_tide_model.py index 54857002b..db8e1feef 100644 --- a/applications/equitypricemodel/tests/test_tide_model.py +++ b/models/tide/tests/test_tide_model.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from equitypricemodel.tide_model import Model, quantile_loss +from tide.tide_model import Model, quantile_loss from tinygrad.tensor import Tensor BATCH_SIZE = 4 diff --git a/applications/equitypricemodel/tests/test_trainer.py b/models/tide/tests/test_trainer.py similarity index 96% rename from applications/equitypricemodel/tests/test_trainer.py rename to models/tide/tests/test_trainer.py index 490d88493..64244e988 100644 --- a/applications/equitypricemodel/tests/test_trainer.py +++ b/models/tide/tests/test_trainer.py @@ -2,7 +2,7 @@ import polars as pl import pytest -from equitypricemodel.trainer import DEFAULT_CONFIGURATION, train_model +from tide.trainer import DEFAULT_CONFIGURATION, train_model PARTIAL_HIDDEN_SIZE = 16 diff --git a/tools/tests/test_training_flow.py b/models/tide/tests/test_workflow.py similarity index 85% rename from tools/tests/test_training_flow.py rename to models/tide/tests/test_workflow.py index a2effa341..cc766105b 100644 --- a/tools/tests/test_training_flow.py +++ b/models/tide/tests/test_workflow.py @@ -4,7 +4,7 @@ import polars as pl import pytest -from tools.flows.training_flow import ( +from tide.workflow import ( prepare_data, sync_equity_bars, sync_equity_details, @@ -15,7 +15,7 @@ LOOKBACK_DAYS = 30 -@patch("tools.flows.training_flow.sync_equity_bars_data") +@patch("tide.workflow.sync_equity_bars_data") def test_sync_equity_bars_calls_sync_with_date_range(mock_sync: MagicMock) -> None: start_date = datetime(2024, 1, 1, tzinfo=UTC) end_date = datetime(2024, 1, 31, tzinfo=UTC) @@ -32,27 +32,27 @@ def test_sync_equity_bars_calls_sync_with_date_range(mock_sync: MagicMock) -> No assert end == end_date -@patch("tools.flows.training_flow.sync_equity_details_data") +@patch("tide.workflow.sync_equity_details_data") def test_sync_equity_details_calls_sync(mock_sync: MagicMock) -> None: sync_equity_details.fn(base_url="http://example.com") mock_sync.assert_called_once_with(base_url="http://example.com") -@patch("tools.flows.training_flow.sync_equity_details_data") +@patch("tide.workflow.sync_equity_details_data") def test_sync_equity_details_ignores_not_implemented(mock_sync: MagicMock) -> None: mock_sync.side_effect = RuntimeError("Sync failed with status 501: not implemented") sync_equity_details.fn(base_url="http://example.com") mock_sync.assert_called_once_with(base_url="http://example.com") -@patch("tools.flows.training_flow.sync_equity_details_data") +@patch("tide.workflow.sync_equity_details_data") def test_sync_equity_details_raises_non_501_errors(mock_sync: MagicMock) -> None: mock_sync.side_effect = RuntimeError("Sync failed with status 500: failure") with pytest.raises(RuntimeError, match="status 500"): sync_equity_details.fn(base_url="http://example.com") -@patch("tools.flows.training_flow.prepare_training_data") +@patch("tide.workflow.prepare_training_data") def test_prepare_data_calls_prepare_training_data(mock_prepare: MagicMock) -> None: start_date = datetime(2024, 1, 1, tzinfo=UTC) end_date = datetime(2024, 1, 31, tzinfo=UTC) @@ -67,7 +67,7 @@ def test_prepare_data_calls_prepare_training_data(mock_prepare: MagicMock) -> No assert result == "training/output.parquet" -@patch("tools.flows.training_flow.prepare_training_data") +@patch("tide.workflow.prepare_training_data") def test_prepare_data_passes_output_key(mock_prepare: MagicMock) -> None: start_date = datetime(2024, 1, 1, tzinfo=UTC) end_date = datetime(2024, 1, 31, tzinfo=UTC) @@ -83,7 +83,7 @@ def test_prepare_data_passes_output_key(mock_prepare: MagicMock) -> None: assert call_kwargs["output_key"] == "custom/key.parquet" -@patch("tools.flows.training_flow.boto3") +@patch("tide.workflow.boto3") def test_train_tide_model_downloads_trains_uploads(mock_boto3: MagicMock) -> None: mock_s3 = MagicMock() mock_boto3.client.return_value = mock_s3 @@ -110,7 +110,7 @@ def test_train_tide_model_downloads_trains_uploads(mock_boto3: MagicMock) -> Non mock_model = MagicMock() mock_data = MagicMock() - with patch("equitypricemodel.trainer.train_model") as mock_train: + with patch("tide.trainer.train_model") as mock_train: mock_train.return_value = (mock_model, mock_data) result = train_tide_model.fn( artifacts_bucket="artifacts-bucket", @@ -124,11 +124,11 @@ def test_train_tide_model_downloads_trains_uploads(mock_boto3: MagicMock) -> Non assert "checkpoint_directory" in mock_train.call_args.kwargs -@patch("tools.flows.training_flow.train_tide_model", return_value="s3://bucket/model") -@patch("tools.flows.training_flow.prepare_data", return_value="training/data.parquet") -@patch("tools.flows.training_flow.sync_equity_details") -@patch("tools.flows.training_flow.sync_equity_bars") -@patch("tools.flows.training_flow.get_training_date_range") +@patch("tide.workflow.train_tide_model", return_value="s3://bucket/model") +@patch("tide.workflow.prepare_data", return_value="training/data.parquet") +@patch("tide.workflow.sync_equity_details") +@patch("tide.workflow.sync_equity_bars") +@patch("tide.workflow.get_training_date_range") def test_training_pipeline_threads_data_key( mock_date_range: MagicMock, mock_bars: MagicMock, diff --git a/pyproject.toml b/pyproject.toml index ad11c94df..81ae4cc89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,23 @@ [tool.uv.workspace] members = [ - "applications/portfoliomanager", - "applications/equitypricemodel", + "applications/portfolio_manager", + "applications/ensemble_manager", "libraries/python", "infrastructure", "tools", + "models/tide", ] [dependency-groups] dev = ["coverage>=7.8.0", "pytest>=8.3.5", "behave>=1.2.6"] [tool.pytest.ini_options] -testpaths = ["applications/*/tests", "libraries/python/tests", "tools/tests"] +testpaths = [ + "applications/*/tests", + "libraries/python/tests", + "tools/tests", + "models/tide/tests", +] python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] diff --git a/tools/pyproject.toml b/tools/pyproject.toml index b0ebeb456..3f84e5b5a 100644 --- a/tools/pyproject.toml +++ b/tools/pyproject.toml @@ -5,7 +5,6 @@ description = "Project tools and scripts" requires-python = "==3.12.10" dependencies = [ "boto3>=1.40.74", - "prefect>=3.0.0,<4.0.0", "structlog>=25.5.0", "requests>=2.32.5", "polars>=1.29.0", diff --git a/uv.lock b/uv.lock index 6d029d2d6..f5d3a6bdb 100644 --- a/uv.lock +++ b/uv.lock @@ -2,25 +2,26 @@ version = 1 revision = 3 requires-python = "==3.12.10" resolution-markers = [ - "sys_platform == 'win32'", - "sys_platform == 'emscripten'", - "sys_platform != 'emscripten' and sys_platform != 'win32'", + "sys_platform == 'win32'", + "sys_platform == 'emscripten'", + "sys_platform != 'emscripten' and sys_platform != 'win32'", ] [manifest] members = [ - "equitypricemodel", - "infrastructure", - "internal", - "portfoliomanager", - "tools", + "ensemble-manager", + "infrastructure", + "internal", + "portfolio-manager", + "tide", + "tools", ] [manifest.dependency-groups] dev = [ - { name = "behave", specifier = ">=1.2.6" }, - { name = "coverage", specifier = ">=7.8.0" }, - { name = "pytest", specifier = ">=8.3.5" }, + { name = "behave", specifier = ">=1.2.6" }, + { name = "coverage", specifier = ">=7.8.0" }, + { name = "pytest", specifier = ">=8.3.5" }, ] [[package]] @@ -29,7 +30,7 @@ version = "0.22.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, ] [[package]] @@ -37,13 +38,13 @@ name = "alembic" version = "1.18.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mako" }, - { name = "sqlalchemy" }, - { name = "typing-extensions" }, + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, ] [[package]] @@ -51,16 +52,16 @@ name = "alpaca-py" version = "0.43.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "msgpack" }, - { name = "pandas" }, - { name = "pydantic" }, - { name = "requests" }, - { name = "sseclient-py" }, - { name = "websockets" }, + { name = "msgpack" }, + { name = "pandas" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "sseclient-py" }, + { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/9e/b49e3a74a9a3c745438a30a324d430ab91fad20a5aa8a59a27e88ff65c0b/alpaca_py-0.43.2.tar.gz", hash = "sha256:e03c7845a9ac6b5581c31f007fe0671f0c536538f130eb8407890a4ba37ee866", size = 97963, upload-time = "2025-11-04T06:14:31.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/ea/dac50720ee46f63b0c6339014b28a3dc976b87e314c2418f3ae5ee7e13f0/alpaca_py-0.43.2-py3-none-any.whl", hash = "sha256:ee608d9744b57766dcce60ff88523073fad798a7361c9bf1ec7a499eec5f19e5", size = 122502, upload-time = "2025-11-04T06:14:30.279Z" }, + { url = "https://files.pythonhosted.org/packages/25/ea/dac50720ee46f63b0c6339014b28a3dc976b87e314c2418f3ae5ee7e13f0/alpaca_py-0.43.2-py3-none-any.whl", hash = "sha256:ee608d9744b57766dcce60ff88523073fad798a7361c9bf1ec7a499eec5f19e5", size = 122502, upload-time = "2025-11-04T06:14:30.279Z" }, ] [[package]] @@ -69,7 +70,7 @@ version = "1.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3c/f3/92e68da6feca11b8d279da0b8f413d35395eef12d243d0c6826924e6f04d/amplitude_analytics-1.2.2.tar.gz", hash = "sha256:ed82b39aa04447e5f156398249d632a5b51591905ec16556c7641a3258d38366", size = 22262, upload-time = "2026-02-17T17:17:09.637Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/c0/1486294b558353b0ee91cac7e876b594b61824e19fcbd4c3f9e7f749a3b8/amplitude_analytics-1.2.2-py3-none-any.whl", hash = "sha256:18f1b6e03cf360fcbea4bffb3a44883f063039ea1622fc85b0da945bdd13f6c6", size = 24660, upload-time = "2026-02-17T17:17:08.8Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c0/1486294b558353b0ee91cac7e876b594b61824e19fcbd4c3f9e7f749a3b8/amplitude_analytics-1.2.2-py3-none-any.whl", hash = "sha256:18f1b6e03cf360fcbea4bffb3a44883f063039ea1622fc85b0da945bdd13f6c6", size = 24660, upload-time = "2026-02-17T17:17:08.8Z" }, ] [[package]] @@ -78,7 +79,7 @@ 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, upload-time = "2025-11-10T22:07:42.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, ] [[package]] @@ -87,17 +88,20 @@ 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" } 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, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "anyio" version = "4.12.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "idna" }, { name = "typing-extensions" }] +dependencies = [ + { name = "idna" }, + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] [[package]] @@ -105,17 +109,17 @@ name = "apprise" version = "1.9.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, - { name = "click" }, - { name = "markdown" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "requests-oauthlib" }, - { name = "tzdata", marker = "sys_platform == 'win32'" }, + { name = "certifi" }, + { name = "click" }, + { name = "markdown" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "requests-oauthlib" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/f5/97dc06b3401bb67abcef6e8bef7155f192b75795c2a2aa4d59eb5aa7fa66/apprise-1.9.7.tar.gz", hash = "sha256:2f73cc1e0264fb119fdb9b7cde82e8fde40a0f531ac885d8c6f0cf0f6e13aec2", size = 1937173, upload-time = "2026-01-20T18:51:32.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/6b/cfa80a13437896eb8f4504ddac6dfa4ef7f1d2b2261057aa4a30003b8de6/apprise-1.9.7-py3-none-any.whl", hash = "sha256:c7640a81a1097685de66e0508e3da89f49235d566cb44bbead1dd98419bf5ee3", size = 1459879, upload-time = "2026-01-20T18:51:30.766Z" }, + { url = "https://files.pythonhosted.org/packages/fb/6b/cfa80a13437896eb8f4504ddac6dfa4ef7f1d2b2261057aa4a30003b8de6/apprise-1.9.7-py3-none-any.whl", hash = "sha256:c7640a81a1097685de66e0508e3da89f49235d566cb44bbead1dd98419bf5ee3", size = 1459879, upload-time = "2026-01-20T18:51:30.766Z" }, ] [[package]] @@ -124,17 +128,19 @@ version = "2.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3b/58/ba011f3cf8291804ce80f9d81289ac15f0319a27f9d7e3c124aa5e4981cc/Arpeggio-2.0.3.tar.gz", hash = "sha256:9e85ad35cfc6c938676817c7ae9a1000a7c72a34c71db0c687136c460d12b85e", size = 766566, upload-time = "2025-09-12T12:45:20.594Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/4d/53b8186b41842f7a5e971b1d1c28e678364dcf841e4170f5d14d38ac1e2a/Arpeggio-2.0.3-py2.py3-none-any.whl", hash = "sha256:9374d9c531b62018b787635f37fd81c9a6ee69ef2d28c5db3cd18791b1f7db2f", size = 54656, upload-time = "2025-09-12T12:45:17.971Z" }, + { url = "https://files.pythonhosted.org/packages/84/4d/53b8186b41842f7a5e971b1d1c28e678364dcf841e4170f5d14d38ac1e2a/Arpeggio-2.0.3-py2.py3-none-any.whl", hash = "sha256:9374d9c531b62018b787635f37fd81c9a6ee69ef2d28c5db3cd18791b1f7db2f", size = 54656, upload-time = "2025-09-12T12:45:17.971Z" }, ] [[package]] name = "asgi-lifespan" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "sniffio" }] +dependencies = [ + { name = "sniffio" }, +] sdist = { url = "https://files.pythonhosted.org/packages/6a/da/e7908b54e0f8043725a990bf625f2041ecf6bfe8eb7b19407f1c00b630f7/asgi-lifespan-2.1.0.tar.gz", hash = "sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308", size = 15627, upload-time = "2023-03-28T17:35:49.126Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/f5/c36551e93acba41a59939ae6a0fb77ddb3f2e8e8caa716410c65f7341f72/asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f", size = 10895, upload-time = "2023-03-28T17:35:47.772Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f5/c36551e93acba41a59939ae6a0fb77ddb3f2e8e8caa716410c65f7341f72/asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f", size = 10895, upload-time = "2023-03-28T17:35:47.772Z" }, ] [[package]] @@ -143,14 +149,14 @@ version = "0.31.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, - { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, - { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, + { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, + { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, ] [[package]] @@ -159,7 +165,7 @@ version = "25.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] @@ -168,7 +174,7 @@ version = "0.22.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, ] [[package]] @@ -176,16 +182,16 @@ name = "behave" version = "1.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama" }, - { name = "cucumber-expressions" }, - { name = "cucumber-tag-expressions" }, - { name = "parse" }, - { name = "parse-type" }, - { name = "six" }, + { name = "colorama" }, + { name = "cucumber-expressions" }, + { name = "cucumber-tag-expressions" }, + { name = "parse" }, + { name = "parse-type" }, + { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/51/f37442fe648b3e35ecf69bee803fa6db3f74c5b46d6c882d0bc5654185a2/behave-1.3.3.tar.gz", hash = "sha256:2b8f4b64ed2ea756a5a2a73e23defc1c4631e9e724c499e46661778453ebaf51", size = 892639, upload-time = "2025-09-04T12:12:02.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/71/06f74ffed6d74525c5cd6677c97bd2df0b7649e47a249cf6a0c2038083b2/behave-1.3.3-py2.py3-none-any.whl", hash = "sha256:89bdb62af8fb9f147ce245736a5de69f025e5edfb66f1fbe16c5007493f842c0", size = 223594, upload-time = "2025-09-04T12:12:00.3Z" }, + { url = "https://files.pythonhosted.org/packages/63/71/06f74ffed6d74525c5cd6677c97bd2df0b7649e47a249cf6a0c2038083b2/behave-1.3.3-py2.py3-none-any.whl", hash = "sha256:89bdb62af8fb9f147ce245736a5de69f025e5edfb66f1fbe16c5007493f842c0", size = 223594, upload-time = "2025-09-04T12:12:00.3Z" }, ] [[package]] @@ -193,50 +199,60 @@ name = "boto3" version = "1.42.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/58/15/356d38280ce3fce37a8e2b44e2ead81240d933f64411e86415a2ed4c0bd5/boto3-1.42.61.tar.gz", hash = "sha256:117ebfc597c95bfb64c6d37ba77bd1c2a97a1885c1dcac2a8be1a14e2139a76d", size = 112750, upload-time = "2026-03-04T20:30:53.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/d7/a2fa875cb7c5d6b5c5cf6fc181343708c8dc6cafae3e6964ed486ae21bea/boto3-1.42.61-py3-none-any.whl", hash = "sha256:156efcc298a33206be6dfd220815c64aa8b09424017534cabe717636961fc306", size = 140555, upload-time = "2026-03-04T20:30:51.17Z" }, + { url = "https://files.pythonhosted.org/packages/66/d7/a2fa875cb7c5d6b5c5cf6fc181343708c8dc6cafae3e6964ed486ae21bea/boto3-1.42.61-py3-none-any.whl", hash = "sha256:156efcc298a33206be6dfd220815c64aa8b09424017534cabe717636961fc306", size = 140555, upload-time = "2026-03-04T20:30:51.17Z" }, ] [[package]] name = "boto3-stubs" version = "1.42.61" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "botocore-stubs" }, { name = "types-s3transfer" }] +dependencies = [ + { name = "botocore-stubs" }, + { name = "types-s3transfer" }, +] sdist = { url = "https://files.pythonhosted.org/packages/f1/f6/07d823217af2023f4a5ac1ef0b7370f1375d31d40d2208438b2e7446670c/boto3_stubs-1.42.61.tar.gz", hash = "sha256:e8c87eeb4a27932e02d647c795693ac075e6eb148084d00959c61f2f64e30e78", size = 100995, upload-time = "2026-03-04T21:01:25.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/f3/3ae7094d0f5c33fb0f80cb96a991abdbe87d49477cc8cdfe3f2648e81382/boto3_stubs-1.42.61-py3-none-any.whl", hash = "sha256:f4cfd8cb90302824cf299253db6162f27b1281e528939ee3e32ddba19a697924", size = 69825, upload-time = "2026-03-04T21:01:12.318Z" }, + { url = "https://files.pythonhosted.org/packages/50/f3/3ae7094d0f5c33fb0f80cb96a991abdbe87d49477cc8cdfe3f2648e81382/boto3_stubs-1.42.61-py3-none-any.whl", hash = "sha256:f4cfd8cb90302824cf299253db6162f27b1281e528939ee3e32ddba19a697924", size = 69825, upload-time = "2026-03-04T21:01:12.318Z" }, ] [package.optional-dependencies] -s3 = [{ name = "mypy-boto3-s3" }] -ssm = [{ name = "mypy-boto3-ssm" }] +s3 = [ + { name = "mypy-boto3-s3" }, +] +ssm = [ + { name = "mypy-boto3-ssm" }, +] [[package]] name = "botocore" version = "1.42.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/6a/27836dde004717c496f69f4fe28fa2f3f3762d04859a9292681944a45a36/botocore-1.42.61.tar.gz", hash = "sha256:702d6011ace2b5b652a0dbb45053d4d9f79da2c5b184463042434e1754bdd601", size = 14954743, upload-time = "2026-03-04T20:30:41.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl", hash = "sha256:476059beb3f462042742950cf195d26bc313461a77189c16e37e205b0a924b26", size = 14627717, upload-time = "2026-03-04T20:30:37.503Z" }, + { url = "https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl", hash = "sha256:476059beb3f462042742950cf195d26bc313461a77189c16e37e205b0a924b26", size = 14627717, upload-time = "2026-03-04T20:30:37.503Z" }, ] [[package]] name = "botocore-stubs" version = "1.42.41" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "types-awscrt" }] +dependencies = [ + { name = "types-awscrt" }, +] sdist = { url = "https://files.pythonhosted.org/packages/0c/a8/a26608ff39e3a5866c6c79eda10133490205cbddd45074190becece3ff2a/botocore_stubs-1.42.41.tar.gz", hash = "sha256:dbeac2f744df6b814ce83ec3f3777b299a015cbea57a2efc41c33b8c38265825", size = 42411, upload-time = "2026-02-03T20:46:14.479Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/76/cab7af7f16c0b09347f2ebe7ffda7101132f786acb767666dce43055faab/botocore_stubs-1.42.41-py3-none-any.whl", hash = "sha256:9423110fb0e391834bd2ed44ae5f879d8cb370a444703d966d30842ce2bcb5f0", size = 66759, upload-time = "2026-02-03T20:46:13.02Z" }, + { url = "https://files.pythonhosted.org/packages/32/76/cab7af7f16c0b09347f2ebe7ffda7101132f786acb767666dce43055faab/botocore_stubs-1.42.41-py3-none-any.whl", hash = "sha256:9423110fb0e391834bd2ed44ae5f879d8cb370a444703d966d30842ce2bcb5f0", size = 66759, upload-time = "2026-02-03T20:46:13.02Z" }, ] [[package]] @@ -245,7 +261,7 @@ version = "7.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6c/c7/342b33cc6877eebc6c9bb45cb9f78e170e575839699f6f3cc96050176431/cachetools-7.0.2.tar.gz", hash = "sha256:7e7f09a4ca8b791d8bb4864afc71e9c17e607a28e6839ca1a644253c97dbeae0", size = 36983, upload-time = "2026-03-02T19:45:16.926Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/04/4b6968e77c110f12da96fdbfcb39c6557c2e5e81bd7afcf8ed893d5bc588/cachetools-7.0.2-py3-none-any.whl", hash = "sha256:938dcad184827c5e94928c4fd5526e2b46692b7fb1ae94472da9131d0299343c", size = 13793, upload-time = "2026-03-02T19:45:15.495Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/4b6968e77c110f12da96fdbfcb39c6557c2e5e81bd7afcf8ed893d5bc588/cachetools-7.0.2-py3-none-any.whl", hash = "sha256:938dcad184827c5e94928c4fd5526e2b46692b7fb1ae94472da9131d0299343c", size = 13793, upload-time = "2026-03-02T19:45:15.495Z" }, ] [[package]] @@ -254,7 +270,7 @@ version = "2026.2.25" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -262,22 +278,22 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, ] [[package]] @@ -286,33 +302,35 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" version = "8.3.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "colorama", marker = "sys_platform == 'win32'" }] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -321,7 +339,7 @@ version = "3.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] @@ -330,7 +348,7 @@ 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" } 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, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] @@ -339,7 +357,7 @@ version = "4.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a9/57/03591c9dda2f394db126db86c9eb47d37006174d8aaa712e60e1d2376ade/coolname-4.0.0.tar.gz", hash = "sha256:168ec04acbd58c4b7db39c5988a0e9daa204631dc975367adeb762b2c880d767", size = 38143, upload-time = "2026-02-22T14:10:17.655Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/96/a4501854ec7178a8c1ccafa457f44b1a28b25b816a7407ba71b816216f32/coolname-4.0.0-py3-none-any.whl", hash = "sha256:6eb1d5471b40b718d26ae7f25466e76fd54eb27f816d67051a9cc4f3f690f940", size = 39761, upload-time = "2026-02-22T14:10:15.955Z" }, + { url = "https://files.pythonhosted.org/packages/1e/96/a4501854ec7178a8c1ccafa457f44b1a28b25b816a7407ba71b816216f32/coolname-4.0.0-py3-none-any.whl", hash = "sha256:6eb1d5471b40b718d26ae7f25466e76fd54eb27f816d67051a9cc4f3f690f940", size = 39761, upload-time = "2026-02-22T14:10:15.955Z" }, ] [[package]] @@ -348,32 +366,35 @@ version = "7.13.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] name = "croniter" version = "6.0.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "python-dateutil" }, { name = "pytz" }] +dependencies = [ + { name = "python-dateutil" }, + { name = "pytz" }, +] sdist = { url = "https://files.pythonhosted.org/packages/ad/2f/44d1ae153a0e27be56be43465e5cb39b9650c781e001e7864389deb25090/croniter-6.0.0.tar.gz", hash = "sha256:37c504b313956114a983ece2c2b07790b1f1094fe9d81cc94739214748255577", size = 64481, upload-time = "2024-12-17T17:17:47.32Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/4b/290b4c3efd6417a8b0c284896de19b1d5855e6dbdb97d2a35e68fa42de85/croniter-6.0.0-py2.py3-none-any.whl", hash = "sha256:2f878c3856f17896979b2a4379ba1f09c83e374931ea15cc835c5dd2eee9b368", size = 25468, upload-time = "2024-12-17T17:17:45.359Z" }, + { url = "https://files.pythonhosted.org/packages/07/4b/290b4c3efd6417a8b0c284896de19b1d5855e6dbdb97d2a35e68fa42de85/croniter-6.0.0-py2.py3-none-any.whl", hash = "sha256:2f878c3856f17896979b2a4379ba1f09c83e374931ea15cc835c5dd2eee9b368", size = 25468, upload-time = "2024-12-17T17:17:45.359Z" }, ] [[package]] @@ -381,38 +402,38 @@ name = "cryptography" version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, - { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, ] [[package]] @@ -421,7 +442,7 @@ version = "19.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/81/5f/1afc1a0a2a6daed47b2d032a897613a556ebf49303e4af8310223f4a450b/cucumber_expressions-19.0.0.tar.gz", hash = "sha256:8eb5ae46dd03dd37fec1163ace1510529501d7d1868ff372c1ab2cd5aa4543a8", size = 13722, upload-time = "2026-01-25T18:09:15.642Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/72/eb79377be899d24c91ed196a50808563685992bb3aa6b82dbe3a1e30df67/cucumber_expressions-19.0.0-py3-none-any.whl", hash = "sha256:f452e6c73258c1677043ad67ad5f538c87284d6b502004720510fb6b7452d9c5", size = 20232, upload-time = "2026-01-25T18:09:16.763Z" }, + { url = "https://files.pythonhosted.org/packages/3b/72/eb79377be899d24c91ed196a50808563685992bb3aa6b82dbe3a1e30df67/cucumber_expressions-19.0.0-py3-none-any.whl", hash = "sha256:f452e6c73258c1677043ad67ad5f538c87284d6b502004720510fb6b7452d9c5", size = 20232, upload-time = "2026-01-25T18:09:16.763Z" }, ] [[package]] @@ -430,7 +451,7 @@ version = "9.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b1/e0/de0b292a533846def28a4373a00c883ffa5ed986ca79f0284bd69a6297b8/cucumber_tag_expressions-9.1.0.tar.gz", hash = "sha256:d960383d5885300ebcbcb14e41657946fde2a59d5c0f485eb291bc6a0e228acc", size = 8437, upload-time = "2026-02-17T21:59:06.072Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/cf/8e8d034f7d55fceb2e4765bf9fab5da6d6a09204cd09de7bb5054f242cd0/cucumber_tag_expressions-9.1.0-py3-none-any.whl", hash = "sha256:cca145d677a942c1877e5a2cf13da8c6ec99260988877c817efd284d8455bb56", size = 9726, upload-time = "2026-02-17T21:59:04.755Z" }, + { url = "https://files.pythonhosted.org/packages/6b/cf/8e8d034f7d55fceb2e4765bf9fab5da6d6a09204cd09de7bb5054f242cd0/cucumber_tag_expressions-9.1.0-py3-none-any.whl", hash = "sha256:cca145d677a942c1877e5a2cf13da8c6ec99260988877c817efd284d8455bb56", size = 9726, upload-time = "2026-02-17T21:59:04.755Z" }, ] [[package]] @@ -438,14 +459,14 @@ name = "cyclopts" version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs" }, - { name = "docstring-parser" }, - { name = "rich" }, - { name = "rich-rst" }, + { name = "attrs" }, + { name = "docstring-parser" }, + { name = "rich" }, + { name = "rich-rst" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/5c/88a4068c660a096bbe87efc5b7c190080c9e86919c36ec5f092cb08d852f/cyclopts-4.6.0.tar.gz", hash = "sha256:483c4704b953ea6da742e8de15972f405d2e748d19a848a4d61595e8e5360ee5", size = 162724, upload-time = "2026-02-23T15:44:49.286Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/eb/1e8337755a70dc7d7ff10a73dc8f20e9352c9ad6c2256ed863ac95cd3539/cyclopts-4.6.0-py3-none-any.whl", hash = "sha256:0a891cb55bfd79a3cdce024db8987b33316aba11071e5258c21ac12a640ba9f2", size = 200518, upload-time = "2026-02-23T15:44:47.854Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/1e8337755a70dc7d7ff10a73dc8f20e9352c9ad6c2256ed863ac95cd3539/cyclopts-4.6.0-py3-none-any.whl", hash = "sha256:0a891cb55bfd79a3cdce024db8987b33316aba11071e5258c21ac12a640ba9f2", size = 200518, upload-time = "2026-02-23T15:44:47.854Z" }, ] [[package]] @@ -453,14 +474,14 @@ name = "dateparser" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "regex" }, - { name = "tzlocal" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "regex" }, + { name = "tzlocal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/668dfb8c073a5dde3efb80fa382de1502e3b14002fd386a8c1b0b49e92a9/dateparser-1.3.0.tar.gz", hash = "sha256:5bccf5d1ec6785e5be71cc7ec80f014575a09b4923e762f850e57443bddbf1a5", size = 337152, upload-time = "2026-02-04T16:00:06.162Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/c7/95349670e193b2891176e1b8e5f43e12b31bff6d9994f70e74ab385047f6/dateparser-1.3.0-py3-none-any.whl", hash = "sha256:8dc678b0a526e103379f02ae44337d424bd366aac727d3c6cf52ce1b01efbb5a", size = 318688, upload-time = "2026-02-04T16:00:04.652Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c7/95349670e193b2891176e1b8e5f43e12b31bff6d9994f70e74ab385047f6/dateparser-1.3.0-py3-none-any.whl", hash = "sha256:8dc678b0a526e103379f02ae44337d424bd366aac727d3c6cf52ce1b01efbb5a", size = 318688, upload-time = "2026-02-04T16:00:04.652Z" }, ] [[package]] @@ -469,11 +490,11 @@ version = "1.8.20" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, ] [[package]] @@ -482,7 +503,7 @@ version = "0.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, ] [[package]] @@ -490,13 +511,13 @@ name = "docker" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "requests" }, - { name = "urllib3" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, ] [[package]] @@ -505,7 +526,7 @@ version = "0.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, ] [[package]] @@ -514,51 +535,43 @@ version = "0.22.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] [[package]] -name = "equitypricemodel" +name = "ensemble-manager" version = "0.0.1" -source = { editable = "applications/equitypricemodel" } -dependencies = [ - { name = "boto3" }, - { name = "fastapi" }, - { name = "internal" }, - { name = "numpy" }, - { name = "pandera", extra = [ - "polars", - ] }, - { name = "polars" }, - { name = "requests" }, - { name = "sentry-sdk", extra = [ - "fastapi", - ] }, - { name = "structlog" }, - { name = "tinygrad" }, - { name = "uvicorn" }, +source = { editable = "applications/ensemble_manager" } +dependencies = [ + { name = "boto3" }, + { name = "fastapi" }, + { name = "internal" }, + { name = "pandera", extra = ["polars"] }, + { name = "polars" }, + { name = "requests" }, + { name = "sentry-sdk", extra = ["fastapi"] }, + { name = "structlog" }, + { name = "tide" }, + { name = "uvicorn" }, ] [package.dev-dependencies] -dev = [{ name = "boto3-stubs", extra = ["s3", "ssm"] }] +dev = [ + { name = "boto3-stubs", extra = ["s3", "ssm"] }, +] [package.metadata] requires-dist = [ - { name = "boto3", specifier = ">=1.35.0" }, - { name = "fastapi", specifier = ">=0.115.0" }, - { name = "internal", editable = "libraries/python" }, - { name = "numpy", specifier = ">=1.26.4" }, - { name = "pandera", extras = [ - "polars", - ], specifier = ">=0.26.0" }, - { name = "polars", specifier = ">=1.29.0" }, - { name = "requests", specifier = ">=2.32.5" }, - { name = "sentry-sdk", extras = [ - "fastapi", - ], specifier = ">=2.0.0" }, - { name = "structlog", specifier = ">=25.5.0" }, - { name = "tinygrad", specifier = ">=0.10.3" }, - { name = "uvicorn", specifier = ">=0.34.0" }, + { name = "boto3", specifier = ">=1.35.0" }, + { name = "fastapi", specifier = ">=0.115.0" }, + { name = "internal", editable = "libraries/python" }, + { name = "pandera", extras = ["polars"], specifier = ">=0.26.0" }, + { name = "polars", specifier = ">=1.29.0" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.0.0" }, + { name = "structlog", specifier = ">=25.5.0" }, + { name = "tide", editable = "models/tide" }, + { name = "uvicorn", specifier = ">=0.34.0" }, ] [package.metadata.requires-dev] @@ -568,39 +581,46 @@ dev = [{ name = "boto3-stubs", extras = ["s3", "ssm"], specifier = ">=1.38.0" }] name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "typing-extensions" }] +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, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] name = "fakeredis" version = "2.34.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "redis" }, { name = "sortedcontainers" }] +dependencies = [ + { name = "redis" }, + { name = "sortedcontainers" }, +] sdist = { url = "https://files.pythonhosted.org/packages/11/40/fd09efa66205eb32253d2b2ebc63537281384d2040f0a88bcd2289e120e4/fakeredis-2.34.1.tar.gz", hash = "sha256:4ff55606982972eecce3ab410e03d746c11fe5deda6381d913641fbd8865ea9b", size = 177315, upload-time = "2026-02-25T13:17:51.315Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/b5/82f89307d0d769cd9bf46a54fb9136be08e4e57c5570ae421db4c9a2ba62/fakeredis-2.34.1-py3-none-any.whl", hash = "sha256:0107ec99d48913e7eec2a5e3e2403d1bd5f8aa6489d1a634571b975289c48f12", size = 122160, upload-time = "2026-02-25T13:17:49.701Z" }, + { url = "https://files.pythonhosted.org/packages/49/b5/82f89307d0d769cd9bf46a54fb9136be08e4e57c5570ae421db4c9a2ba62/fakeredis-2.34.1-py3-none-any.whl", hash = "sha256:0107ec99d48913e7eec2a5e3e2403d1bd5f8aa6489d1a634571b975289c48f12", size = 122160, upload-time = "2026-02-25T13:17:49.701Z" }, ] [package.optional-dependencies] -lua = [{ name = "lupa" }] +lua = [ + { name = "lupa" }, +] [[package]] name = "fastapi" version = "0.135.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-doc" }, - { name = "pydantic" }, - { name = "starlette" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/7b/f8e0211e9380f7195ba3f3d40c292594fd81ba8ec4629e3854c353aaca45/fastapi-0.135.1.tar.gz", hash = "sha256:d04115b508d936d254cea545b7312ecaa58a7b3a0f84952535b4c9afae7668cd", size = 394962, upload-time = "2026-03-01T18:18:29.369Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" }, ] [[package]] @@ -609,7 +629,7 @@ version = "2026.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, ] [[package]] @@ -618,7 +638,7 @@ version = "0.21" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, ] [[package]] @@ -627,33 +647,39 @@ version = "3.3.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, - { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, - { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, - { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, - { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, ] [[package]] name = "griffe" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "griffecli" }, { name = "griffelib" }] +dependencies = [ + { name = "griffecli" }, + { name = "griffelib" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" }, + { url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" }, ] [[package]] name = "griffecli" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "colorama" }, { name = "griffelib" }] +dependencies = [ + { name = "colorama" }, + { name = "griffelib" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" }, ] [[package]] @@ -661,26 +687,28 @@ name = "griffelib" version = "2.0.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, + { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, ] [[package]] name = "grpcio" version = "1.78.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "typing-extensions" }] +dependencies = [ + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, - { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, - { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, - { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, ] [[package]] @@ -689,17 +717,20 @@ version = "0.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "h2" version = "4.3.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "hpack" }, { name = "hyperframe" }] +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, ] [[package]] @@ -708,17 +739,20 @@ version = "4.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, ] [[package]] name = "httpcore" version = "1.0.9" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "certifi" }, { name = "h11" }] +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] @@ -726,18 +760,20 @@ name = "httpx" version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [package.optional-dependencies] -http2 = [{ name = "h2" }] +http2 = [ + { name = "h2" }, +] [[package]] name = "humanize" @@ -745,7 +781,7 @@ version = "4.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, ] [[package]] @@ -754,7 +790,7 @@ version = "6.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, ] [[package]] @@ -763,17 +799,19 @@ version = "3.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] name = "importlib-metadata" version = "8.7.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "zipp" }] +dependencies = [ + { name = "zipp" }, +] sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] [[package]] @@ -781,24 +819,24 @@ name = "infrastructure" version = "0.0.1" source = { virtual = "infrastructure" } dependencies = [ - { name = "pip" }, - { name = "protobuf" }, - { name = "pulumi" }, - { name = "pulumi-aws" }, - { name = "pulumi-command" }, - { name = "pulumi-docker" }, - { name = "pulumi-tls" }, + { name = "pip" }, + { name = "protobuf" }, + { name = "pulumi" }, + { name = "pulumi-aws" }, + { name = "pulumi-command" }, + { name = "pulumi-docker" }, + { name = "pulumi-tls" }, ] [package.metadata] requires-dist = [ - { name = "pip", specifier = ">=25.3,<26.0" }, - { name = "protobuf", specifier = ">=5.29.5,<6.0.0" }, - { name = "pulumi", specifier = ">=3.189.0" }, - { name = "pulumi-aws", specifier = ">=7.4.0" }, - { name = "pulumi-command", specifier = ">=1.1.0" }, - { name = "pulumi-docker", specifier = ">=4.10.0" }, - { name = "pulumi-tls", specifier = ">=5.2.1" }, + { name = "pip", specifier = ">=25.3,<26.0" }, + { name = "protobuf", specifier = ">=5.29.5,<6.0.0" }, + { name = "pulumi", specifier = ">=3.189.0" }, + { name = "pulumi-aws", specifier = ">=7.4.0" }, + { name = "pulumi-command", specifier = ">=1.1.0" }, + { name = "pulumi-docker", specifier = ">=4.10.0" }, + { name = "pulumi-tls", specifier = ">=5.2.1" }, ] [[package]] @@ -807,41 +845,49 @@ version = "2.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "internal" version = "0.0.1" source = { editable = "libraries/python" } -dependencies = [{ name = "pandera", extra = ["polars"] }, { name = "polars" }] +dependencies = [ + { name = "pandera", extra = ["polars"] }, + { name = "polars" }, + { name = "structlog" }, +] [package.metadata] requires-dist = [ - { name = "pandera", extras = [ - "polars", - ], specifier = ">=0.26.0" }, - { name = "polars", specifier = ">=1.29.0" }, + { name = "pandera", extras = ["polars"], specifier = ">=0.26.0" }, + { name = "polars", specifier = ">=1.29.0" }, + { name = "structlog", specifier = ">=25.5.0" }, ] [[package]] name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "markupsafe" }] +dependencies = [ + { name = "markupsafe" }, +] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "jinja2-humanize-extension" version = "0.4.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "humanize" }, { name = "jinja2" }] +dependencies = [ + { name = "humanize" }, + { name = "jinja2" }, +] sdist = { url = "https://files.pythonhosted.org/packages/74/77/0bba383819dd4e67566487c11c49479ced87e77c3285d8e7f7a3401cf882/jinja2_humanize_extension-0.4.0.tar.gz", hash = "sha256:e7d69b1c20f32815bbec722330ee8af14b1287bb1c2b0afa590dbf031cadeaa0", size = 4746, upload-time = "2023-09-01T12:52:42.781Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/b4/08c9d297edd5e1182506edecccbb88a92e1122a057953068cadac420ca5d/jinja2_humanize_extension-0.4.0-py3-none-any.whl", hash = "sha256:b6326e2da0f7d425338bebf58848e830421defbce785f12ae812e65128518156", size = 4769, upload-time = "2023-09-01T12:52:41.098Z" }, + { url = "https://files.pythonhosted.org/packages/26/b4/08c9d297edd5e1182506edecccbb88a92e1122a057953068cadac420ca5d/jinja2_humanize_extension-0.4.0-py3-none-any.whl", hash = "sha256:b6326e2da0f7d425338bebf58848e830421defbce785f12ae812e65128518156", size = 4769, upload-time = "2023-09-01T12:52:41.098Z" }, ] [[package]] @@ -850,17 +896,19 @@ version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, ] [[package]] name = "jsonpatch" version = "1.33" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "jsonpointer" }] +dependencies = [ + { name = "jsonpointer" }, +] sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, ] [[package]] @@ -869,7 +917,7 @@ version = "3.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] [[package]] @@ -877,24 +925,26 @@ name = "jsonschema" version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] [[package]] name = "jsonschema-specifications" version = "2025.9.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "referencing" }] +dependencies = [ + { name = "referencing" }, +] sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] [[package]] @@ -903,27 +953,29 @@ version = "2.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/191c3e6ec6502e3dbe25a53e27f69a5daeac3e56de1f73c0138224171ead/lupa-2.6.tar.gz", hash = "sha256:9a770a6e89576be3447668d7ced312cd6fd41d3c13c2462c9dc2c2ab570e45d9", size = 7240282, upload-time = "2025-10-24T07:20:29.738Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/86/ce243390535c39d53ea17ccf0240815e6e457e413e40428a658ea4ee4b8d/lupa-2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47ce718817ef1cc0c40d87c3d5ae56a800d61af00fbc0fad1ca9be12df2f3b56", size = 951707, upload-time = "2025-10-24T07:18:03.884Z" }, - { url = "https://files.pythonhosted.org/packages/86/85/cedea5e6cbeb54396fdcc55f6b741696f3f036d23cfaf986d50d680446da/lupa-2.6-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7aba985b15b101495aa4b07112cdc08baa0c545390d560ad5cfde2e9e34f4d58", size = 1916703, upload-time = "2025-10-24T07:18:05.6Z" }, - { url = "https://files.pythonhosted.org/packages/24/be/3d6b5f9a8588c01a4d88129284c726017b2089f3a3fd3ba8bd977292fea0/lupa-2.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:b766f62f95b2739f2248977d29b0722e589dcf4f0ccfa827ccbd29f0148bd2e5", size = 985152, upload-time = "2025-10-24T07:18:08.561Z" }, - { url = "https://files.pythonhosted.org/packages/eb/23/9f9a05beee5d5dce9deca4cb07c91c40a90541fc0a8e09db4ee670da550f/lupa-2.6-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:00a934c23331f94cb51760097ebfab14b005d55a6b30a2b480e3c53dd2fa290d", size = 1159599, upload-time = "2025-10-24T07:18:10.346Z" }, - { url = "https://files.pythonhosted.org/packages/40/4e/e7c0583083db9d7f1fd023800a9767d8e4391e8330d56c2373d890ac971b/lupa-2.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21de9f38bd475303e34a042b7081aabdf50bd9bafd36ce4faea2f90fd9f15c31", size = 1038686, upload-time = "2025-10-24T07:18:12.112Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/5a4f7d959d4feba5e203ff0c31889e74d1ca3153122be4a46dca7d92bf7c/lupa-2.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf3bda96d3fc41237e964a69c23647d50d4e28421111360274d4799832c560e9", size = 2071956, upload-time = "2025-10-24T07:18:14.572Z" }, - { url = "https://files.pythonhosted.org/packages/92/34/2f4f13ca65d01169b1720176aedc4af17bc19ee834598c7292db232cb6dc/lupa-2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a76ead245da54801a81053794aa3975f213221f6542d14ec4b859ee2e7e0323", size = 1057199, upload-time = "2025-10-24T07:18:16.379Z" }, - { url = "https://files.pythonhosted.org/packages/35/2a/5f7d2eebec6993b0dcd428e0184ad71afb06a45ba13e717f6501bfed1da3/lupa-2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8dd0861741caa20886ddbda0a121d8e52fb9b5bb153d82fa9bba796962bf30e8", size = 1173693, upload-time = "2025-10-24T07:18:18.153Z" }, - { url = "https://files.pythonhosted.org/packages/e4/29/089b4d2f8e34417349af3904bb40bec40b65c8731f45e3fd8d497ca573e5/lupa-2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:239e63948b0b23023f81d9a19a395e768ed3da6a299f84e7963b8f813f6e3f9c", size = 2164394, upload-time = "2025-10-24T07:18:20.403Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1b/79c17b23c921f81468a111cad843b076a17ef4b684c4a8dff32a7969c3f0/lupa-2.6-cp312-cp312-win32.whl", hash = "sha256:325894e1099499e7a6f9c351147661a2011887603c71086d36fe0f964d52d1ce", size = 1420647, upload-time = "2025-10-24T07:18:23.368Z" }, - { url = "https://files.pythonhosted.org/packages/b8/15/5121e68aad3584e26e1425a5c9a79cd898f8a152292059e128c206ee817c/lupa-2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c735a1ce8ee60edb0fe71d665f1e6b7c55c6021f1d340eb8c865952c602cd36f", size = 1688529, upload-time = "2025-10-24T07:18:25.523Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/ce243390535c39d53ea17ccf0240815e6e457e413e40428a658ea4ee4b8d/lupa-2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47ce718817ef1cc0c40d87c3d5ae56a800d61af00fbc0fad1ca9be12df2f3b56", size = 951707, upload-time = "2025-10-24T07:18:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/cedea5e6cbeb54396fdcc55f6b741696f3f036d23cfaf986d50d680446da/lupa-2.6-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7aba985b15b101495aa4b07112cdc08baa0c545390d560ad5cfde2e9e34f4d58", size = 1916703, upload-time = "2025-10-24T07:18:05.6Z" }, + { url = "https://files.pythonhosted.org/packages/24/be/3d6b5f9a8588c01a4d88129284c726017b2089f3a3fd3ba8bd977292fea0/lupa-2.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:b766f62f95b2739f2248977d29b0722e589dcf4f0ccfa827ccbd29f0148bd2e5", size = 985152, upload-time = "2025-10-24T07:18:08.561Z" }, + { url = "https://files.pythonhosted.org/packages/eb/23/9f9a05beee5d5dce9deca4cb07c91c40a90541fc0a8e09db4ee670da550f/lupa-2.6-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:00a934c23331f94cb51760097ebfab14b005d55a6b30a2b480e3c53dd2fa290d", size = 1159599, upload-time = "2025-10-24T07:18:10.346Z" }, + { url = "https://files.pythonhosted.org/packages/40/4e/e7c0583083db9d7f1fd023800a9767d8e4391e8330d56c2373d890ac971b/lupa-2.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21de9f38bd475303e34a042b7081aabdf50bd9bafd36ce4faea2f90fd9f15c31", size = 1038686, upload-time = "2025-10-24T07:18:12.112Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/5a4f7d959d4feba5e203ff0c31889e74d1ca3153122be4a46dca7d92bf7c/lupa-2.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf3bda96d3fc41237e964a69c23647d50d4e28421111360274d4799832c560e9", size = 2071956, upload-time = "2025-10-24T07:18:14.572Z" }, + { url = "https://files.pythonhosted.org/packages/92/34/2f4f13ca65d01169b1720176aedc4af17bc19ee834598c7292db232cb6dc/lupa-2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a76ead245da54801a81053794aa3975f213221f6542d14ec4b859ee2e7e0323", size = 1057199, upload-time = "2025-10-24T07:18:16.379Z" }, + { url = "https://files.pythonhosted.org/packages/35/2a/5f7d2eebec6993b0dcd428e0184ad71afb06a45ba13e717f6501bfed1da3/lupa-2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8dd0861741caa20886ddbda0a121d8e52fb9b5bb153d82fa9bba796962bf30e8", size = 1173693, upload-time = "2025-10-24T07:18:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/e4/29/089b4d2f8e34417349af3904bb40bec40b65c8731f45e3fd8d497ca573e5/lupa-2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:239e63948b0b23023f81d9a19a395e768ed3da6a299f84e7963b8f813f6e3f9c", size = 2164394, upload-time = "2025-10-24T07:18:20.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1b/79c17b23c921f81468a111cad843b076a17ef4b684c4a8dff32a7969c3f0/lupa-2.6-cp312-cp312-win32.whl", hash = "sha256:325894e1099499e7a6f9c351147661a2011887603c71086d36fe0f964d52d1ce", size = 1420647, upload-time = "2025-10-24T07:18:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/b8/15/5121e68aad3584e26e1425a5c9a79cd898f8a152292059e128c206ee817c/lupa-2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c735a1ce8ee60edb0fe71d665f1e6b7c55c6021f1d340eb8c865952c602cd36f", size = 1688529, upload-time = "2025-10-24T07:18:25.523Z" }, ] [[package]] name = "mako" version = "1.3.10" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "markupsafe" }] +dependencies = [ + { name = "markupsafe" }, +] sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, ] [[package]] @@ -932,17 +984,19 @@ version = "3.10.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] name = "markdown-it-py" version = "4.0.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "mdurl" }] +dependencies = [ + { name = "mdurl" }, +] sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [[package]] @@ -951,17 +1005,17 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, ] [[package]] @@ -970,7 +1024,7 @@ 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" } 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, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] @@ -979,15 +1033,15 @@ version = "1.1.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, - { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, - { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, ] [[package]] @@ -996,7 +1050,7 @@ version = "1.42.37" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e6/41/44066f4cd3421bacb6aad4ec7b1da8d0f8858560e526166db64d95fa7ad7/mypy_boto3_s3-1.42.37.tar.gz", hash = "sha256:628a4652f727870a07e1c3854d6f30dc545a7dd5a4b719a2c59c32a95d92e4c1", size = 76317, upload-time = "2026-01-28T20:51:52.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/06/cb6050ecd72f5fa449bac80ad1a4711719367c4f545201317f36e3999784/mypy_boto3_s3-1.42.37-py3-none-any.whl", hash = "sha256:7c118665f3f583dbfde1013ce47908749f9d2a760f28f59ec65732306ee9cec9", size = 83439, upload-time = "2026-01-28T20:51:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/94/06/cb6050ecd72f5fa449bac80ad1a4711719367c4f545201317f36e3999784/mypy_boto3_s3-1.42.37-py3-none-any.whl", hash = "sha256:7c118665f3f583dbfde1013ce47908749f9d2a760f28f59ec65732306ee9cec9", size = 83439, upload-time = "2026-01-28T20:51:49.99Z" }, ] [[package]] @@ -1005,7 +1059,7 @@ version = "1.42.54" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/e9/cde8a9fe2bf061e595256e5542f4c803efdcb2f741611bcae9763f2af993/mypy_boto3_ssm-1.42.54.tar.gz", hash = "sha256:f4bc19a08635757808b66ef94a5b52c3729da998587745962626e60606a1be2c", size = 94255, upload-time = "2026-02-20T20:49:58.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/54/58fa9dba05049adbfd5ecbd755a2e730c118d682367d885e8b35e9b9f0cf/mypy_boto3_ssm-1.42.54-py3-none-any.whl", hash = "sha256:dfd70aa5f60be70437b53482fa6e183bafe922598a50fc6c51f6ad3bd70d8c04", size = 95951, upload-time = "2026-02-20T20:49:54.202Z" }, + { url = "https://files.pythonhosted.org/packages/ef/54/58fa9dba05049adbfd5ecbd755a2e730c118d682367d885e8b35e9b9f0cf/mypy_boto3_ssm-1.42.54-py3-none-any.whl", hash = "sha256:dfd70aa5f60be70437b53482fa6e183bafe922598a50fc6c51f6ad3bd70d8c04", size = 95951, upload-time = "2026-02-20T20:49:54.202Z" }, ] [[package]] @@ -1014,7 +1068,7 @@ version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] @@ -1023,17 +1077,17 @@ version = "2.4.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, ] [[package]] @@ -1042,17 +1096,20 @@ version = "3.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, ] [[package]] name = "opentelemetry-api" version = "1.40.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "importlib-metadata" }, { name = "typing-extensions" }] +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, ] [[package]] @@ -1061,21 +1118,21 @@ version = "3.11.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, - { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, - { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, - { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, - { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, - { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, - { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, - { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, ] [[package]] @@ -1084,7 +1141,7 @@ version = "25.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -1092,20 +1149,20 @@ name = "pandas" version = "3.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, - { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, - { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, - { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, - { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, ] [[package]] @@ -1113,19 +1170,21 @@ name = "pandera" version = "0.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "packaging" }, - { name = "pydantic" }, - { name = "typeguard" }, - { name = "typing-extensions" }, - { name = "typing-inspect" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "typeguard" }, + { name = "typing-extensions" }, + { name = "typing-inspect" }, ] sdist = { url = "https://files.pythonhosted.org/packages/80/ee/8e0d40dad2c0947b933fc9c0959b2c17cc3419ccdf50df683216f37a3f96/pandera-0.29.0.tar.gz", hash = "sha256:06bc4fc1e4ff02534dd44482a9bc704fb2e58fe3fbb11be906aa714f7f5ec801", size = 575324, upload-time = "2026-01-29T02:49:36.891Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/7b/03299e4ccc5e3cfb0f9e234207ac43ef08b3ba6c4c2882c890e550ceadba/pandera-0.29.0-py3-none-any.whl", hash = "sha256:b3b25d6c00d7c100fbab96aff0e81e52d3dae543a880d24135cca705fa97c516", size = 295876, upload-time = "2026-01-29T02:49:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/75/7b/03299e4ccc5e3cfb0f9e234207ac43ef08b3ba6c4c2882c890e550ceadba/pandera-0.29.0-py3-none-any.whl", hash = "sha256:b3b25d6c00d7c100fbab96aff0e81e52d3dae543a880d24135cca705fa97c516", size = 295876, upload-time = "2026-01-29T02:49:34.812Z" }, ] [package.optional-dependencies] -polars = [{ name = "polars" }] +polars = [ + { name = "polars" }, +] [[package]] name = "parse" @@ -1133,27 +1192,33 @@ version = "1.21.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fd/18/0bea374e5ec3c8ba15365570002187f3fef9d7265ffbc2f649529878cc80/parse-1.21.1.tar.gz", hash = "sha256:825e1a88e9d9fb481b8d2ca709c6195558b6eaa97c559ad3a9a20aa2d12815a3", size = 29105, upload-time = "2026-02-19T02:20:07.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/13/114daf766c33aec6c5a3954e7ea653f8a7ade9602c5c5a2228281698c490/parse-1.21.1-py2.py3-none-any.whl", hash = "sha256:55339ca698019815df3b8e8b550e5933933527e623b0cdf1ca2f404da35ffb47", size = 19693, upload-time = "2026-02-19T02:20:06.575Z" }, + { url = "https://files.pythonhosted.org/packages/c3/13/114daf766c33aec6c5a3954e7ea653f8a7ade9602c5c5a2228281698c490/parse-1.21.1-py2.py3-none-any.whl", hash = "sha256:55339ca698019815df3b8e8b550e5933933527e623b0cdf1ca2f404da35ffb47", size = 19693, upload-time = "2026-02-19T02:20:06.575Z" }, ] [[package]] name = "parse-type" version = "0.6.6" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "parse" }, { name = "six" }] +dependencies = [ + { name = "parse" }, + { name = "six" }, +] sdist = { url = "https://files.pythonhosted.org/packages/19/ea/42ba6ce0abba04ab6e0b997dcb9b528a4661b62af1fe1b0d498120d5ea78/parse_type-0.6.6.tar.gz", hash = "sha256:513a3784104839770d690e04339a8b4d33439fcd5dd99f2e4580f9fc1097bfb2", size = 98012, upload-time = "2025-08-11T22:53:48.066Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/8d/eef3d8cdccc32abdd91b1286884c99b8c3a6d3b135affcc2a7a0f383bb32/parse_type-0.6.6-py2.py3-none-any.whl", hash = "sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c", size = 27085, upload-time = "2025-08-11T22:53:46.396Z" }, + { url = "https://files.pythonhosted.org/packages/85/8d/eef3d8cdccc32abdd91b1286884c99b8c3a6d3b135affcc2a7a0f383bb32/parse_type-0.6.6-py2.py3-none-any.whl", hash = "sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c", size = 27085, upload-time = "2025-08-11T22:53:46.396Z" }, ] [[package]] name = "parver" version = "0.5" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "arpeggio" }, { name = "attrs" }] +dependencies = [ + { name = "arpeggio" }, + { name = "attrs" }, +] sdist = { url = "https://files.pythonhosted.org/packages/cc/e5/1c774688a90f0b76e872e30f6f1ba3f5e14056cd0d96a684047d4a986226/parver-0.5.tar.gz", hash = "sha256:b9fde1e6bb9ce9f07e08e9c4bea8d8825c5e78e18a0052d02e02bf9517eb4777", size = 26908, upload-time = "2023-10-03T21:06:54.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/4c/f98024021bef4d44dce3613feebd702c7ad8883f777ff8488384c59e9774/parver-0.5-py3-none-any.whl", hash = "sha256:2281b187276c8e8e3c15634f62287b2fb6fe0efe3010f739a6bd1e45fa2bf2b2", size = 15172, upload-time = "2023-10-03T21:06:52.796Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/f98024021bef4d44dce3613feebd702c7ad8883f777ff8488384c59e9774/parver-0.5-py3-none-any.whl", hash = "sha256:2281b187276c8e8e3c15634f62287b2fb6fe0efe3010f739a6bd1e45fa2bf2b2", size = 15172, upload-time = "2023-10-03T21:06:52.796Z" }, ] [[package]] @@ -1162,27 +1227,30 @@ 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, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "pendulum" version = "3.2.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "python-dateutil" }, { name = "tzdata" }] +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] sdist = { url = "https://files.pythonhosted.org/packages/cb/72/9a51afa0a822b09e286c4cb827ed7b00bc818dac7bd11a5f161e493a217d/pendulum-3.2.0.tar.gz", hash = "sha256:e80feda2d10fa3ff8b1526715f7d33dcb7e08494b3088f2c8a3ac92d4a4331ce", size = 86912, upload-time = "2026-01-30T11:22:24.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/56/dd0ea9f97d25a0763cda09e2217563b45714786118d8c68b0b745395d6eb/pendulum-3.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bf0b489def51202a39a2a665dcc4162d5e46934a740fe4c4fe3068979610156c", size = 337830, upload-time = "2026-01-30T11:21:08.298Z" }, - { url = "https://files.pythonhosted.org/packages/cf/98/83d62899bf7226fc12396de4bc1fb2b5da27e451c7c60790043aaf8b4731/pendulum-3.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:937a529aa302efa18dcf25e53834964a87ffb2df8f80e3669ab7757a6126beaf", size = 327574, upload-time = "2026-01-30T11:21:09.715Z" }, - { url = "https://files.pythonhosted.org/packages/76/fa/ff2aa992b23f0543c709b1a3f3f9ed760ec71fd02c8bb01f93bf008b52e4/pendulum-3.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85c7689defc65c4dc29bf257f7cca55d210fabb455de9476e1748d2ab2ae80d7", size = 339891, upload-time = "2026-01-30T11:21:11.089Z" }, - { url = "https://files.pythonhosted.org/packages/c5/4e/25b4fa11d19503d50d7b52d7ef943c0f20fd54422aaeb9e38f588c815c50/pendulum-3.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e216e5a412563ea2ecf5de467dcf3d02717947fcdabe6811d5ee360726b02b", size = 373726, upload-time = "2026-01-30T11:21:12.493Z" }, - { url = "https://files.pythonhosted.org/packages/4f/30/0acad6396c4e74e5c689aa4f0b0c49e2ecdcfce368e7b5bf35ca1c0fc61a/pendulum-3.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a2af22eeec438fbaac72bb7fba783e0950a514fba980d9a32db394b51afccec", size = 379827, upload-time = "2026-01-30T11:21:14.08Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f7/e6a2fdf2a23d59b4b48b8fa89e8d4bf2dd371aea2c6ba8fcecec20a4acb9/pendulum-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3159cceb54f5aa8b85b141c7f0ce3fac8bdd1ffdc7c79e67dca9133eac7c4d11", size = 348921, upload-time = "2026-01-30T11:21:15.816Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f2/c15fa7f9ad4e181aa469b6040b574988bd108ccdf4ae509ad224f9e4db44/pendulum-3.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c39ea5e9ffa20ea8bae986d00e0908bd537c8468b71d6b6503ab0b4c3d76e0ea", size = 517188, upload-time = "2026-01-30T11:21:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/47/c7/5f80b12ee88ec26e930c3a5a602608a63c29cf60c81a0eb066d583772550/pendulum-3.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e5afc753e570cce1f44197676371f68953f7d4f022303d141bb09f804d5fe6d7", size = 561833, upload-time = "2026-01-30T11:21:19.232Z" }, - { url = "https://files.pythonhosted.org/packages/90/15/1ac481626cb63db751f6281e294661947c1f0321ebe5d1c532a3b51a8006/pendulum-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:fd55c12560816d9122ca2142d9e428f32c0c083bf77719320b1767539c7a3a3b", size = 258725, upload-time = "2026-01-30T11:21:20.558Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/50b0398d7d027eb70a3e1e336de7b6e599c6b74431cb7d3863287e1292bb/pendulum-3.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:faef52a7ed99729f0838353b956f3fabf6c550c062db247e9e2fc2b48fcb9457", size = 253089, upload-time = "2026-01-30T11:21:22.497Z" }, - { url = "https://files.pythonhosted.org/packages/02/fb/d65db067a67df7252f18b0cb7420dda84078b9e8bfb375215469c14a50be/pendulum-3.2.0-py3-none-any.whl", hash = "sha256:f3a9c18a89b4d9ef39c5fa6a78722aaff8d5be2597c129a3b16b9f40a561acf3", size = 114111, upload-time = "2026-01-30T11:22:22.361Z" }, + { url = "https://files.pythonhosted.org/packages/41/56/dd0ea9f97d25a0763cda09e2217563b45714786118d8c68b0b745395d6eb/pendulum-3.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bf0b489def51202a39a2a665dcc4162d5e46934a740fe4c4fe3068979610156c", size = 337830, upload-time = "2026-01-30T11:21:08.298Z" }, + { url = "https://files.pythonhosted.org/packages/cf/98/83d62899bf7226fc12396de4bc1fb2b5da27e451c7c60790043aaf8b4731/pendulum-3.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:937a529aa302efa18dcf25e53834964a87ffb2df8f80e3669ab7757a6126beaf", size = 327574, upload-time = "2026-01-30T11:21:09.715Z" }, + { url = "https://files.pythonhosted.org/packages/76/fa/ff2aa992b23f0543c709b1a3f3f9ed760ec71fd02c8bb01f93bf008b52e4/pendulum-3.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85c7689defc65c4dc29bf257f7cca55d210fabb455de9476e1748d2ab2ae80d7", size = 339891, upload-time = "2026-01-30T11:21:11.089Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4e/25b4fa11d19503d50d7b52d7ef943c0f20fd54422aaeb9e38f588c815c50/pendulum-3.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e216e5a412563ea2ecf5de467dcf3d02717947fcdabe6811d5ee360726b02b", size = 373726, upload-time = "2026-01-30T11:21:12.493Z" }, + { url = "https://files.pythonhosted.org/packages/4f/30/0acad6396c4e74e5c689aa4f0b0c49e2ecdcfce368e7b5bf35ca1c0fc61a/pendulum-3.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a2af22eeec438fbaac72bb7fba783e0950a514fba980d9a32db394b51afccec", size = 379827, upload-time = "2026-01-30T11:21:14.08Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f7/e6a2fdf2a23d59b4b48b8fa89e8d4bf2dd371aea2c6ba8fcecec20a4acb9/pendulum-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3159cceb54f5aa8b85b141c7f0ce3fac8bdd1ffdc7c79e67dca9133eac7c4d11", size = 348921, upload-time = "2026-01-30T11:21:15.816Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f2/c15fa7f9ad4e181aa469b6040b574988bd108ccdf4ae509ad224f9e4db44/pendulum-3.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c39ea5e9ffa20ea8bae986d00e0908bd537c8468b71d6b6503ab0b4c3d76e0ea", size = 517188, upload-time = "2026-01-30T11:21:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/47/c7/5f80b12ee88ec26e930c3a5a602608a63c29cf60c81a0eb066d583772550/pendulum-3.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e5afc753e570cce1f44197676371f68953f7d4f022303d141bb09f804d5fe6d7", size = 561833, upload-time = "2026-01-30T11:21:19.232Z" }, + { url = "https://files.pythonhosted.org/packages/90/15/1ac481626cb63db751f6281e294661947c1f0321ebe5d1c532a3b51a8006/pendulum-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:fd55c12560816d9122ca2142d9e428f32c0c083bf77719320b1767539c7a3a3b", size = 258725, upload-time = "2026-01-30T11:21:20.558Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/50b0398d7d027eb70a3e1e336de7b6e599c6b74431cb7d3863287e1292bb/pendulum-3.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:faef52a7ed99729f0838353b956f3fabf6c550c062db247e9e2fc2b48fcb9457", size = 253089, upload-time = "2026-01-30T11:21:22.497Z" }, + { url = "https://files.pythonhosted.org/packages/02/fb/d65db067a67df7252f18b0cb7420dda84078b9e8bfb375215469c14a50be/pendulum-3.2.0-py3-none-any.whl", hash = "sha256:f3a9c18a89b4d9ef39c5fa6a78722aaff8d5be2597c129a3b16b9f40a561acf3", size = 114111, upload-time = "2026-01-30T11:22:22.361Z" }, ] [[package]] @@ -1191,7 +1259,7 @@ version = "25.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fe/6e/74a3f0179a4a73a53d66ce57fdb4de0080a8baa1de0063de206d6167acc2/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343", size = 1803014, upload-time = "2025-10-25T00:55:41.394Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, + { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, ] [[package]] @@ -1200,17 +1268,19 @@ version = "1.6.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "polars" version = "1.38.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "polars-runtime-32" }] +dependencies = [ + { name = "polars-runtime-32" }, +] sdist = { url = "https://files.pythonhosted.org/packages/c6/5e/208a24471a433bcd0e9a6889ac49025fd4daad2815c8220c5bd2576e5f1b/polars-1.38.1.tar.gz", hash = "sha256:803a2be5344ef880ad625addfb8f641995cfd777413b08a10de0897345778239", size = 717667, upload-time = "2026-02-06T18:13:23.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl", hash = "sha256:a29479c48fed4984d88b656486d221f638cba45d3e961631a50ee5fdde38cb2c", size = 810368, upload-time = "2026-02-06T18:11:55.819Z" }, + { url = "https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl", hash = "sha256:a29479c48fed4984d88b656486d221f638cba45d3e961631a50ee5fdde38cb2c", size = 810368, upload-time = "2026-02-06T18:11:55.819Z" }, ] [[package]] @@ -1219,55 +1289,47 @@ version = "1.38.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/07/4b/04d6b3fb7cf336fbe12fbc4b43f36d1783e11bb0f2b1e3980ec44878df06/polars_runtime_32-1.38.1.tar.gz", hash = "sha256:04f20ed1f5c58771f34296a27029dc755a9e4b1390caeaef8f317e06fdfce2ec", size = 2812631, upload-time = "2026-02-06T18:13:25.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/a2/a00defbddadd8cf1042f52380dcba6b6592b03bac8e3b34c436b62d12d3b/polars_runtime_32-1.38.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:18154e96044724a0ac38ce155cf63aa03c02dd70500efbbf1a61b08cadd269ef", size = 44108001, upload-time = "2026-02-06T18:11:58.127Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c49acac34cc4049ed188f1eb67d6ff3971a39b4af7f7b734b367119970f313ac", size = 40230140, upload-time = "2026-02-06T18:12:01.181Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fef2ef2626a954e010e006cc8e4de467ecf32d08008f130cea1c78911f545323", size = 41994039, upload-time = "2026-02-06T18:12:04.332Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5f7a8125e2d50e2e060296551c929aec09be23a9edcb2b12ca923f555a5ba", size = 45755804, upload-time = "2026-02-06T18:12:07.846Z" }, - { url = "https://files.pythonhosted.org/packages/91/54/02cd4074c98c361ccd3fec3bcb0bd68dbc639c0550c42a4436b0ff0f3ccf/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:10d19cd9863e129273b18b7fcaab625b5c8143c2d22b3e549067b78efa32e4fa", size = 42159605, upload-time = "2026-02-06T18:12:10.919Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f3/b2a5e720cc56eaa38b4518e63aa577b4bbd60e8b05a00fe43ca051be5879/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61e8d73c614b46a00d2f853625a7569a2e4a0999333e876354ac81d1bf1bb5e2", size = 45336615, upload-time = "2026-02-06T18:12:14.074Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8d/ee2e4b7de948090cfb3df37d401c521233daf97bfc54ddec5d61d1d31618/polars_runtime_32-1.38.1-cp310-abi3-win_amd64.whl", hash = "sha256:08c2b3b93509c1141ac97891294ff5c5b0c548a373f583eaaea873a4bf506437", size = 45680732, upload-time = "2026-02-06T18:12:19.097Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/72c216f4ab0c82b907009668f79183ae029116ff0dd245d56ef58aac48e7/polars_runtime_32-1.38.1-cp310-abi3-win_arm64.whl", hash = "sha256:6d07d0cc832bfe4fb54b6e04218c2c27afcfa6b9498f9f6bbf262a00d58cc7c4", size = 41639413, upload-time = "2026-02-06T18:12:22.044Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a2/a00defbddadd8cf1042f52380dcba6b6592b03bac8e3b34c436b62d12d3b/polars_runtime_32-1.38.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:18154e96044724a0ac38ce155cf63aa03c02dd70500efbbf1a61b08cadd269ef", size = 44108001, upload-time = "2026-02-06T18:11:58.127Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c49acac34cc4049ed188f1eb67d6ff3971a39b4af7f7b734b367119970f313ac", size = 40230140, upload-time = "2026-02-06T18:12:01.181Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fef2ef2626a954e010e006cc8e4de467ecf32d08008f130cea1c78911f545323", size = 41994039, upload-time = "2026-02-06T18:12:04.332Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a5f7a8125e2d50e2e060296551c929aec09be23a9edcb2b12ca923f555a5ba", size = 45755804, upload-time = "2026-02-06T18:12:07.846Z" }, + { url = "https://files.pythonhosted.org/packages/91/54/02cd4074c98c361ccd3fec3bcb0bd68dbc639c0550c42a4436b0ff0f3ccf/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:10d19cd9863e129273b18b7fcaab625b5c8143c2d22b3e549067b78efa32e4fa", size = 42159605, upload-time = "2026-02-06T18:12:10.919Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/b2a5e720cc56eaa38b4518e63aa577b4bbd60e8b05a00fe43ca051be5879/polars_runtime_32-1.38.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61e8d73c614b46a00d2f853625a7569a2e4a0999333e876354ac81d1bf1bb5e2", size = 45336615, upload-time = "2026-02-06T18:12:14.074Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/ee2e4b7de948090cfb3df37d401c521233daf97bfc54ddec5d61d1d31618/polars_runtime_32-1.38.1-cp310-abi3-win_amd64.whl", hash = "sha256:08c2b3b93509c1141ac97891294ff5c5b0c548a373f583eaaea873a4bf506437", size = 45680732, upload-time = "2026-02-06T18:12:19.097Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/72c216f4ab0c82b907009668f79183ae029116ff0dd245d56ef58aac48e7/polars_runtime_32-1.38.1-cp310-abi3-win_arm64.whl", hash = "sha256:6d07d0cc832bfe4fb54b6e04218c2c27afcfa6b9498f9f6bbf262a00d58cc7c4", size = 41639413, upload-time = "2026-02-06T18:12:22.044Z" }, ] [[package]] -name = "portfoliomanager" +name = "portfolio-manager" version = "0.0.1" -source = { editable = "applications/portfoliomanager" } -dependencies = [ - { name = "alpaca-py" }, - { name = "fastapi" }, - { name = "httpx" }, - { name = "internal" }, - { name = "pandera", extra = [ - "polars", - ] }, - { name = "polars" }, - { name = "requests" }, - { name = "scipy" }, - { name = "sentry-sdk", extra = [ - "fastapi", - ] }, - { name = "structlog" }, - { name = "uvicorn" }, +source = { editable = "applications/portfolio_manager" } +dependencies = [ + { name = "alpaca-py" }, + { name = "fastapi" }, + { name = "httpx" }, + { name = "internal" }, + { name = "pandera", extra = ["polars"] }, + { name = "polars" }, + { name = "requests" }, + { name = "scipy" }, + { name = "sentry-sdk", extra = ["fastapi"] }, + { name = "structlog" }, + { name = "uvicorn" }, ] [package.metadata] requires-dist = [ - { name = "alpaca-py", specifier = ">=0.42.1" }, - { name = "fastapi", specifier = ">=0.115.0" }, - { name = "httpx", specifier = ">=0.27.0" }, - { name = "internal", editable = "libraries/python" }, - { name = "pandera", extras = [ - "polars", - ], specifier = ">=0.26.0" }, - { name = "polars", specifier = ">=1.29.0" }, - { name = "requests", specifier = ">=2.32.5" }, - { name = "scipy", specifier = ">=1.17.1" }, - { name = "sentry-sdk", extras = [ - "fastapi", - ], specifier = ">=2.0.0" }, - { name = "structlog", specifier = ">=25.5.0" }, - { name = "uvicorn", specifier = ">=0.34.0" }, + { name = "alpaca-py", specifier = ">=0.42.1" }, + { name = "fastapi", specifier = ">=0.115.0" }, + { name = "httpx", specifier = ">=0.27.0" }, + { name = "internal", editable = "libraries/python" }, + { name = "pandera", extras = ["polars"], specifier = ">=0.26.0" }, + { name = "polars", specifier = ">=1.29.0" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "scipy", specifier = ">=1.17.1" }, + { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.0.0" }, + { name = "structlog", specifier = ">=25.5.0" }, + { name = "uvicorn", specifier = ">=0.34.0" }, ] [[package]] @@ -1275,69 +1337,65 @@ name = "prefect" version = "3.6.20" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiosqlite" }, - { name = "alembic" }, - { name = "amplitude-analytics" }, - { name = "anyio" }, - { name = "apprise" }, - { name = "asgi-lifespan" }, - { name = "asyncpg" }, - { name = "cachetools" }, - { name = "click" }, - { name = "cloudpickle" }, - { name = "coolname" }, - { name = "cryptography" }, - { name = "cyclopts" }, - { name = "dateparser" }, - { name = "docker" }, - { name = "exceptiongroup" }, - { name = "fastapi" }, - { name = "fsspec" }, - { name = "graphviz" }, - { name = "griffe" }, - { name = "httpcore" }, - { name = "httpx", extra = [ - "http2", - ] }, - { name = "humanize" }, - { name = "jinja2" }, - { name = "jinja2-humanize-extension" }, - { name = "jsonpatch" }, - { name = "jsonschema" }, - { name = "opentelemetry-api" }, - { name = "orjson" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "pendulum" }, - { name = "pluggy" }, - { name = "prometheus-client" }, - { name = "pydantic" }, - { name = "pydantic-core" }, - { name = "pydantic-extra-types" }, - { name = "pydantic-settings" }, - { name = "pydocket" }, - { name = "python-dateutil" }, - { name = "python-slugify" }, - { name = "pytz" }, - { name = "pyyaml" }, - { name = "readchar" }, - { name = "rfc3339-validator" }, - { name = "rich" }, - { name = "ruamel-yaml" }, - { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, - { name = "semver" }, - { name = "sniffio" }, - { name = "sqlalchemy", extra = [ - "asyncio", - ] }, - { name = "toml" }, - { name = "typing-extensions" }, - { name = "uvicorn" }, - { name = "websockets" }, + { name = "aiosqlite" }, + { name = "alembic" }, + { name = "amplitude-analytics" }, + { name = "anyio" }, + { name = "apprise" }, + { name = "asgi-lifespan" }, + { name = "asyncpg" }, + { name = "cachetools" }, + { name = "click" }, + { name = "cloudpickle" }, + { name = "coolname" }, + { name = "cryptography" }, + { name = "cyclopts" }, + { name = "dateparser" }, + { name = "docker" }, + { name = "exceptiongroup" }, + { name = "fastapi" }, + { name = "fsspec" }, + { name = "graphviz" }, + { name = "griffe" }, + { name = "httpcore" }, + { name = "httpx", extra = ["http2"] }, + { name = "humanize" }, + { name = "jinja2" }, + { name = "jinja2-humanize-extension" }, + { name = "jsonpatch" }, + { name = "jsonschema" }, + { name = "opentelemetry-api" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pendulum" }, + { name = "pluggy" }, + { name = "prometheus-client" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "pydantic-extra-types" }, + { name = "pydantic-settings" }, + { name = "pydocket" }, + { name = "python-dateutil" }, + { name = "python-slugify" }, + { name = "pytz" }, + { name = "pyyaml" }, + { name = "readchar" }, + { name = "rfc3339-validator" }, + { name = "rich" }, + { name = "ruamel-yaml" }, + { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, + { name = "semver" }, + { name = "sniffio" }, + { name = "sqlalchemy", extra = ["asyncio"] }, + { name = "toml" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, + { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/44/ac67f20a513a9a00396d68040bc2fecaabed2cbc5c91b3b8908d56f23dd1/prefect-3.6.20.tar.gz", hash = "sha256:3dec3dd21034ff2659202fdde3a6b663cb53147c75322f56721b610263515759", size = 11146102, upload-time = "2026-02-27T18:09:27.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/c5/51cde4fe1cc8c38b8adb7a3f55938c60bdf808de3fd5fa077a6ce2ed9eba/prefect-3.6.20-py3-none-any.whl", hash = "sha256:07bc3a04bc897c0b22e1555b409c2acbc57cea7f3b2edc2a2c860665a14da6e3", size = 11885881, upload-time = "2026-02-27T18:09:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/50/c5/51cde4fe1cc8c38b8adb7a3f55938c60bdf808de3fd5fa077a6ce2ed9eba/prefect-3.6.20-py3-none-any.whl", hash = "sha256:07bc3a04bc897c0b22e1555b409c2acbc57cea7f3b2edc2a2c860665a14da6e3", size = 11885881, upload-time = "2026-02-27T18:09:24.398Z" }, ] [[package]] @@ -1346,7 +1404,7 @@ version = "0.24.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, ] [[package]] @@ -1355,12 +1413,12 @@ version = "5.29.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/57/394a763c103e0edf87f0938dafcd918d53b4c011dfc5c8ae80f3b0452dbb/protobuf-5.29.6.tar.gz", hash = "sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723", size = 425623, upload-time = "2026-02-04T22:54:40.584Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/88/9ee58ff7863c479d6f8346686d4636dd4c415b0cbeed7a6a7d0617639c2a/protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1", size = 423357, upload-time = "2026-02-04T22:54:25.805Z" }, - { url = "https://files.pythonhosted.org/packages/1c/66/2dc736a4d576847134fb6d80bd995c569b13cdc7b815d669050bf0ce2d2c/protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda", size = 435175, upload-time = "2026-02-04T22:54:28.592Z" }, - { url = "https://files.pythonhosted.org/packages/06/db/49b05966fd208ae3f44dcd33837b6243b4915c57561d730a43f881f24dea/protobuf-5.29.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269", size = 418619, upload-time = "2026-02-04T22:54:30.266Z" }, - { url = "https://files.pythonhosted.org/packages/b7/d7/48cbf6b0c3c39761e47a99cb483405f0fde2be22cf00d71ef316ce52b458/protobuf-5.29.6-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6", size = 320284, upload-time = "2026-02-04T22:54:31.782Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dd/cadd6ec43069247d91f6345fa7a0d2858bef6af366dbd7ba8f05d2c77d3b/protobuf-5.29.6-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9", size = 320478, upload-time = "2026-02-04T22:54:32.909Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cb/e3065b447186cb70aa65acc70c86baf482d82bf75625bf5a2c4f6919c6a3/protobuf-5.29.6-py3-none-any.whl", hash = "sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86", size = 173126, upload-time = "2026-02-04T22:54:39.462Z" }, + { url = "https://files.pythonhosted.org/packages/d4/88/9ee58ff7863c479d6f8346686d4636dd4c415b0cbeed7a6a7d0617639c2a/protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1", size = 423357, upload-time = "2026-02-04T22:54:25.805Z" }, + { url = "https://files.pythonhosted.org/packages/1c/66/2dc736a4d576847134fb6d80bd995c569b13cdc7b815d669050bf0ce2d2c/protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda", size = 435175, upload-time = "2026-02-04T22:54:28.592Z" }, + { url = "https://files.pythonhosted.org/packages/06/db/49b05966fd208ae3f44dcd33837b6243b4915c57561d730a43f881f24dea/protobuf-5.29.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269", size = 418619, upload-time = "2026-02-04T22:54:30.266Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d7/48cbf6b0c3c39761e47a99cb483405f0fde2be22cf00d71ef316ce52b458/protobuf-5.29.6-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6", size = 320284, upload-time = "2026-02-04T22:54:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dd/cadd6ec43069247d91f6345fa7a0d2858bef6af366dbd7ba8f05d2c77d3b/protobuf-5.29.6-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9", size = 320478, upload-time = "2026-02-04T22:54:32.909Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cb/e3065b447186cb70aa65acc70c86baf482d82bf75625bf5a2c4f6919c6a3/protobuf-5.29.6-py3-none-any.whl", hash = "sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86", size = 173126, upload-time = "2026-02-04T22:54:39.462Z" }, ] [[package]] @@ -1368,71 +1426,94 @@ name = "pulumi" version = "3.223.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "debugpy" }, - { name = "dill" }, - { name = "grpcio" }, - { name = "pip" }, - { name = "protobuf" }, - { name = "pyyaml" }, - { name = "semver" }, + { name = "debugpy" }, + { name = "dill" }, + { name = "grpcio" }, + { name = "pip" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "semver" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/86/156a4505c33db96513c4e55ab6110352cafd4199a92b7011dac857ee757b/pulumi-3.223.0-py3-none-any.whl", hash = "sha256:1ade988a885966efc0f77ee9afc8dd2efd863b86951ca70ae28ffe775201c6ad", size = 390346, upload-time = "2026-02-20T02:43:03.333Z" }, + { url = "https://files.pythonhosted.org/packages/b7/86/156a4505c33db96513c4e55ab6110352cafd4199a92b7011dac857ee757b/pulumi-3.223.0-py3-none-any.whl", hash = "sha256:1ade988a885966efc0f77ee9afc8dd2efd863b86951ca70ae28ffe775201c6ad", size = 390346, upload-time = "2026-02-20T02:43:03.333Z" }, ] [[package]] name = "pulumi-aws" version = "7.21.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "parver" }, { name = "pulumi" }, { name = "semver" }] +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] sdist = { url = "https://files.pythonhosted.org/packages/d9/2a/063c3b642d1c80628726f1c26f77d7967d951ecb14ee4bd300ddfce49450/pulumi_aws-7.21.0.tar.gz", hash = "sha256:20a8d3bc427c36eb379faf4a41013b391827ab2674cffb39282442e8a6e08b90", size = 8806146, upload-time = "2026-03-04T20:46:01.886Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/42/ad871813eb3bc748d611d2b4d8418abef720c631bae3f38c04fdef9a8969/pulumi_aws-7.21.0-py3-none-any.whl", hash = "sha256:da5fdf274fa3977e79a7bd7148dfb9a56abbee2062cbf717c5c2335c835de549", size = 11860516, upload-time = "2026-03-04T20:45:59.028Z" }, + { url = "https://files.pythonhosted.org/packages/a2/42/ad871813eb3bc748d611d2b4d8418abef720c631bae3f38c04fdef9a8969/pulumi_aws-7.21.0-py3-none-any.whl", hash = "sha256:da5fdf274fa3977e79a7bd7148dfb9a56abbee2062cbf717c5c2335c835de549", size = 11860516, upload-time = "2026-03-04T20:45:59.028Z" }, ] [[package]] name = "pulumi-command" version = "1.2.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "parver" }, { name = "pulumi" }, { name = "semver" }] +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] sdist = { url = "https://files.pythonhosted.org/packages/cf/56/b7f66c9d2e4536b24a5bbe4e743093c7cdd689343d86f020419f4897a88d/pulumi_command-1.2.1.tar.gz", hash = "sha256:cbc9281a6571701ac5db598dac0246a058875b62f28c40c7cc0fcdc36898918d", size = 33022, upload-time = "2026-03-04T00:23:25.84Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/ce/1e5f30f868ba0827ca3311b0b171b12606e30c05e864714764d8d943fef1/pulumi_command-1.2.1-py3-none-any.whl", hash = "sha256:c7e465338e4363f114ddff99ee249260c7770ef4451b970ab51f7e8a37c9b030", size = 36861, upload-time = "2026-03-04T00:23:24.571Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ce/1e5f30f868ba0827ca3311b0b171b12606e30c05e864714764d8d943fef1/pulumi_command-1.2.1-py3-none-any.whl", hash = "sha256:c7e465338e4363f114ddff99ee249260c7770ef4451b970ab51f7e8a37c9b030", size = 36861, upload-time = "2026-03-04T00:23:24.571Z" }, ] [[package]] name = "pulumi-docker" version = "4.11.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "parver" }, { name = "pulumi" }, { name = "semver" }] +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] sdist = { url = "https://files.pythonhosted.org/packages/7d/00/a3f80e12ce4ab4dece01195e631a854f8910b533787b71d4ecd7669ae70f/pulumi_docker-4.11.0.tar.gz", hash = "sha256:b18fc208531ce2a64d2a898f69c11ff2b792078abde1d9ac3a4625c564467a50", size = 115726, upload-time = "2025-12-24T13:12:14.62Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/d7/3c3cd6b6ec53d1b6914b83ed461da8e86e82208675da43e8cd0a8e45a2c7/pulumi_docker-4.11.0-py3-none-any.whl", hash = "sha256:b421fdee90257ffd65a091b65638d00b8b6827887ccbb62772148d0d563d13d5", size = 137300, upload-time = "2025-12-24T13:12:12.873Z" }, + { url = "https://files.pythonhosted.org/packages/de/d7/3c3cd6b6ec53d1b6914b83ed461da8e86e82208675da43e8cd0a8e45a2c7/pulumi_docker-4.11.0-py3-none-any.whl", hash = "sha256:b421fdee90257ffd65a091b65638d00b8b6827887ccbb62772148d0d563d13d5", size = 137300, upload-time = "2025-12-24T13:12:12.873Z" }, ] [[package]] name = "pulumi-tls" version = "5.3.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "parver" }, { name = "pulumi" }, { name = "semver" }] +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] sdist = { url = "https://files.pythonhosted.org/packages/85/be/2493f951ae78feae990f2c417eba6d21a1b7b307571310f515eb5634cec6/pulumi_tls-5.3.0.tar.gz", hash = "sha256:1f79463570c8f854ec1cb18758a121629b980b97f1d76db92767978c972673d9", size = 27035, upload-time = "2026-01-31T04:24:16.548Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/9d/ba374c13459e0b2dba11d3757922e482f806481e7b88fec0cc1a734c2f54/pulumi_tls-5.3.0-py3-none-any.whl", hash = "sha256:267ecebb2ac5a41fe764c1e08aa9f724a88e0615e731a809e8253a1b50b8f5c8", size = 35517, upload-time = "2026-01-31T04:24:15.242Z" }, + { url = "https://files.pythonhosted.org/packages/86/9d/ba374c13459e0b2dba11d3757922e482f806481e7b88fec0cc1a734c2f54/pulumi_tls-5.3.0-py3-none-any.whl", hash = "sha256:267ecebb2ac5a41fe764c1e08aa9f724a88e0615e731a809e8253a1b50b8f5c8", size = 35517, upload-time = "2026-01-31T04:24:15.242Z" }, ] [[package]] name = "py-key-value-aio" version = "0.4.4" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "beartype" }, { name = "typing-extensions" }] +dependencies = [ + { name = "beartype" }, + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/04/3c/0397c072a38d4bc580994b42e0c90c5f44f679303489e4376289534735e5/py_key_value_aio-0.4.4.tar.gz", hash = "sha256:e3012e6243ed7cc09bb05457bd4d03b1ba5c2b1ca8700096b3927db79ffbbe55", size = 92300, upload-time = "2026-02-16T21:21:43.245Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/69/f1b537ee70b7def42d63124a539ed3026a11a3ffc3086947a1ca6e861868/py_key_value_aio-0.4.4-py3-none-any.whl", hash = "sha256:18e17564ecae61b987f909fc2cd41ee2012c84b4b1dcb8c055cf8b4bc1bf3f5d", size = 152291, upload-time = "2026-02-16T21:21:44.241Z" }, + { url = "https://files.pythonhosted.org/packages/32/69/f1b537ee70b7def42d63124a539ed3026a11a3ffc3086947a1ca6e861868/py_key_value_aio-0.4.4-py3-none-any.whl", hash = "sha256:18e17564ecae61b987f909fc2cd41ee2012c84b4b1dcb8c055cf8b4bc1bf3f5d", size = 152291, upload-time = "2026-02-16T21:21:44.241Z" }, ] [package.optional-dependencies] -memory = [{ name = "cachetools" }] -redis = [{ name = "redis" }] +memory = [ + { name = "cachetools" }, +] +redis = [ + { name = "redis" }, +] [[package]] name = "pycparser" @@ -1440,7 +1521,7 @@ version = "3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -1448,51 +1529,56 @@ name = "pydantic" version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, ] sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [[package]] name = "pydantic-core" version = "2.41.5" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "typing-extensions" }] +dependencies = [ + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { 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, upload-time = "2025-11-04T13:42:49.689Z" }, - { 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, upload-time = "2025-11-04T13:42:52.215Z" }, - { 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, upload-time = "2025-11-04T13:42:56.483Z" }, - { 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, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { 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, upload-time = "2025-11-04T13:42:49.689Z" }, + { 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, upload-time = "2025-11-04T13:42:52.215Z" }, + { 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, upload-time = "2025-11-04T13:42:56.483Z" }, + { 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, upload-time = "2025-11-04T13:42:59.471Z" }, ] [[package]] name = "pydantic-extra-types" version = "2.11.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "pydantic" }, { name = "typing-extensions" }] +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/fd/35/2fee58b1316a73e025728583d3b1447218a97e621933fc776fb8c0f2ebdd/pydantic_extra_types-2.11.0.tar.gz", hash = "sha256:4e9991959d045b75feb775683437a97991d02c138e00b59176571db9ce634f0e", size = 157226, upload-time = "2025-12-31T16:18:27.944Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/17/fabd56da47096d240dd45ba627bead0333b0cf0ee8ada9bec579287dadf3/pydantic_extra_types-2.11.0-py3-none-any.whl", hash = "sha256:84b864d250a0fc62535b7ec591e36f2c5b4d1325fa0017eb8cda9aeb63b374a6", size = 74296, upload-time = "2025-12-31T16:18:26.38Z" }, + { url = "https://files.pythonhosted.org/packages/fe/17/fabd56da47096d240dd45ba627bead0333b0cf0ee8ada9bec579287dadf3/pydantic_extra_types-2.11.0-py3-none-any.whl", hash = "sha256:84b864d250a0fc62535b7ec591e36f2c5b4d1325fa0017eb8cda9aeb63b374a6", size = 74296, upload-time = "2025-12-31T16:18:26.38Z" }, ] [[package]] @@ -1500,13 +1586,13 @@ name = "pydantic-settings" version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, ] [[package]] @@ -1514,28 +1600,23 @@ name = "pydocket" version = "0.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle" }, - { name = "croniter" }, - { name = "fakeredis", extra = [ - "lua", - ] }, - { name = "opentelemetry-api" }, - { name = "prometheus-client" }, - { name = "py-key-value-aio", extra = [ - "memory", - "redis", - ] }, - { name = "python-json-logger" }, - { name = "redis" }, - { name = "rich" }, - { name = "typer" }, - { name = "typing-extensions" }, - { name = "tzdata", marker = "sys_platform == 'win32'" }, - { name = "uncalled-for" }, + { name = "cloudpickle" }, + { name = "croniter" }, + { name = "fakeredis", extra = ["lua"] }, + { name = "opentelemetry-api" }, + { name = "prometheus-client" }, + { name = "py-key-value-aio", extra = ["memory", "redis"] }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "rich" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, + { name = "uncalled-for" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d2/da/5f76e42214c76402e1a2b4b59610211635c1068cab85509c78f1ca49a385/pydocket-0.18.0.tar.gz", hash = "sha256:cd5b6e7386331ca05a0163401f392b08b07e61342b5333c3ece6a7ca5435f984", size = 354637, upload-time = "2026-03-02T16:22:17.356Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/57/ac0d47cd3550d859138647c2c4fbd53a2db05db8729433eaa6128e9964ba/pydocket-0.18.0-py3-none-any.whl", hash = "sha256:d995d9a3c88af0402fda640c18e1b51561041b9e3af1a92dce2fdc6c8f6c7090", size = 98848, upload-time = "2026-03-02T16:22:15.792Z" }, + { url = "https://files.pythonhosted.org/packages/04/57/ac0d47cd3550d859138647c2c4fbd53a2db05db8729433eaa6128e9964ba/pydocket-0.18.0-py3-none-any.whl", hash = "sha256:d995d9a3c88af0402fda640c18e1b51561041b9e3af1a92dce2fdc6c8f6c7090", size = 98848, upload-time = "2026-03-02T16:22:15.792Z" }, ] [[package]] @@ -1544,7 +1625,7 @@ version = "2.19.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -1552,25 +1633,27 @@ name = "pytest" version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "python-dateutil" version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "six" }] +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" } 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, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] @@ -1579,7 +1662,7 @@ version = "1.2.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] @@ -1588,17 +1671,19 @@ version = "4.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, ] [[package]] name = "python-slugify" version = "8.0.4" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "text-unidecode" }] +dependencies = [ + { name = "text-unidecode" }, +] sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] [[package]] @@ -1607,7 +1692,7 @@ version = "2025.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] [[package]] @@ -1615,9 +1700,9 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, ] [[package]] @@ -1626,16 +1711,16 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] [[package]] @@ -1644,7 +1729,7 @@ version = "4.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/dd/f8/8657b8cbb4ebeabfbdf991ac40eca8a1d1bd012011bd44ad1ed10f5cb494/readchar-4.2.1.tar.gz", hash = "sha256:91ce3faf07688de14d800592951e5575e9c7a3213738ed01d394dcc949b79adb", size = 9685, upload-time = "2024-11-04T18:28:07.757Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/10/e4b1e0e5b6b6745c8098c275b69bc9d73e9542d5c7da4f137542b499ed44/readchar-4.2.1-py3-none-any.whl", hash = "sha256:a769305cd3994bb5fa2764aa4073452dc105a4ec39068ffe6efd3c20c60acc77", size = 9350, upload-time = "2024-11-04T18:28:02.859Z" }, + { url = "https://files.pythonhosted.org/packages/a9/10/e4b1e0e5b6b6745c8098c275b69bc9d73e9542d5c7da4f137542b499ed44/readchar-4.2.1-py3-none-any.whl", hash = "sha256:a769305cd3994bb5fa2764aa4073452dc105a4ec39068ffe6efd3c20c60acc77", size = 9350, upload-time = "2024-11-04T18:28:02.859Z" }, ] [[package]] @@ -1653,7 +1738,7 @@ version = "7.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e9/31/1476f206482dd9bc53fdbbe9f6fbd5e05d153f18e54667ce839df331f2e6/redis-7.2.1.tar.gz", hash = "sha256:6163c1a47ee2d9d01221d8456bc1c75ab953cbda18cfbc15e7140e9ba16ca3a5", size = 4906735, upload-time = "2026-02-25T20:05:18.171Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/98/1dd1a5c060916cf21d15e67b7d6a7078e26e2605d5c37cbc9f4f5454c478/redis-7.2.1-py3-none-any.whl", hash = "sha256:49e231fbc8df2001436ae5252b3f0f3dc930430239bfeb6da4c7ee92b16e5d33", size = 396057, upload-time = "2026-02-25T20:05:16.533Z" }, + { url = "https://files.pythonhosted.org/packages/ca/98/1dd1a5c060916cf21d15e67b7d6a7078e26e2605d5c37cbc9f4f5454c478/redis-7.2.1-py3-none-any.whl", hash = "sha256:49e231fbc8df2001436ae5252b3f0f3dc930430239bfeb6da4c7ee92b16e5d33", size = 396057, upload-time = "2026-02-25T20:05:16.533Z" }, ] [[package]] @@ -1661,13 +1746,13 @@ name = "referencing" version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions" }, + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] @@ -1676,22 +1761,22 @@ version = "2026.2.28" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574, upload-time = "2026-02-28T02:16:50.455Z" }, - { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426, upload-time = "2026-02-28T02:16:52.52Z" }, - { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200, upload-time = "2026-02-28T02:16:54.08Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765, upload-time = "2026-02-28T02:16:55.905Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093, upload-time = "2026-02-28T02:16:58.094Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455, upload-time = "2026-02-28T02:17:00.918Z" }, - { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037, upload-time = "2026-02-28T02:17:02.842Z" }, - { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113, upload-time = "2026-02-28T02:17:04.506Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194, upload-time = "2026-02-28T02:17:06.888Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846, upload-time = "2026-02-28T02:17:09.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516, upload-time = "2026-02-28T02:17:11.004Z" }, - { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278, upload-time = "2026-02-28T02:17:12.693Z" }, - { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068, upload-time = "2026-02-28T02:17:14.9Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416, upload-time = "2026-02-28T02:17:17.15Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297, upload-time = "2026-02-28T02:17:18.723Z" }, - { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408, upload-time = "2026-02-28T02:17:20.328Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574, upload-time = "2026-02-28T02:16:50.455Z" }, + { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426, upload-time = "2026-02-28T02:16:52.52Z" }, + { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200, upload-time = "2026-02-28T02:16:54.08Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765, upload-time = "2026-02-28T02:16:55.905Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093, upload-time = "2026-02-28T02:16:58.094Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455, upload-time = "2026-02-28T02:17:00.918Z" }, + { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037, upload-time = "2026-02-28T02:17:02.842Z" }, + { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113, upload-time = "2026-02-28T02:17:04.506Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194, upload-time = "2026-02-28T02:17:06.888Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846, upload-time = "2026-02-28T02:17:09.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516, upload-time = "2026-02-28T02:17:11.004Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278, upload-time = "2026-02-28T02:17:12.693Z" }, + { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068, upload-time = "2026-02-28T02:17:14.9Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416, upload-time = "2026-02-28T02:17:17.15Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297, upload-time = "2026-02-28T02:17:18.723Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408, upload-time = "2026-02-28T02:17:20.328Z" }, ] [[package]] @@ -1699,54 +1784,65 @@ name = "requests" version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] name = "requests-oauthlib" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "oauthlib" }, { name = "requests" }] +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, ] [[package]] name = "rfc3339-validator" version = "0.1.4" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "six" }] +dependencies = [ + { name = "six" }, +] sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, ] [[package]] name = "rich" version = "14.3.3" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "markdown-it-py" }, { name = "pygments" }] +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, ] [[package]] name = "rich-rst" version = "1.3.2" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "docutils" }, { name = "rich" }] +dependencies = [ + { name = "docutils" }, + { name = "rich" }, +] sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, ] [[package]] @@ -1755,21 +1851,21 @@ version = "0.30.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, ] [[package]] @@ -1778,7 +1874,7 @@ version = "0.19.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, ] [[package]] @@ -1787,45 +1883,49 @@ version = "0.2.15" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, - { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, - { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, - { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, - { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, - { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, - { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, - { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, + { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, + { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, + { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, + { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, ] [[package]] name = "s3transfer" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "botocore" }] +dependencies = [ + { name = "botocore" }, +] sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, ] [[package]] name = "scipy" version = "1.17.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "numpy" }] +dependencies = [ + { name = "numpy" }, +] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, ] [[package]] @@ -1834,21 +1934,26 @@ version = "3.0.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, ] [[package]] name = "sentry-sdk" version = "2.54.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "certifi" }, { name = "urllib3" }] +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] sdist = { url = "https://files.pythonhosted.org/packages/c8/e9/2e3a46c304e7fa21eaa70612f60354e32699c7102eb961f67448e222ad7c/sentry_sdk-2.54.0.tar.gz", hash = "sha256:2620c2575128d009b11b20f7feb81e4e4e8ae08ec1d36cbc845705060b45cc1b", size = 413813, upload-time = "2026-03-02T15:12:41.355Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, ] [package.optional-dependencies] -fastapi = [{ name = "fastapi" }] +fastapi = [ + { name = "fastapi" }, +] [[package]] name = "shellingham" @@ -1856,7 +1961,7 @@ 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" } 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, upload-time = "2023-10-24T04:13:38.866Z" }, ] [[package]] @@ -1865,7 +1970,7 @@ version = "1.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] @@ -1874,7 +1979,7 @@ version = "1.3.1" 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" } 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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] @@ -1883,7 +1988,7 @@ version = "2.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] [[package]] @@ -1891,40 +1996,45 @@ name = "sqlalchemy" version = "2.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, - { name = "typing-extensions" }, + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, - { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, - { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, - { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, + { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, + { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, ] [package.optional-dependencies] -asyncio = [{ name = "greenlet" }] +asyncio = [ + { name = "greenlet" }, +] [[package]] name = "sseclient-py" version = "1.9.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/2e/59920f7d66b7f9932a3d83dd0ec53fab001be1e058bf582606fe414a5198/sseclient_py-1.9.0-py3-none-any.whl", hash = "sha256:340062b1587fc2880892811e2ab5b176d98ef3eee98b3672ff3a3ba1e8ed0f6f", size = 8351, upload-time = "2026-01-02T23:39:30.995Z" }, + { url = "https://files.pythonhosted.org/packages/4d/2e/59920f7d66b7f9932a3d83dd0ec53fab001be1e058bf582606fe414a5198/sseclient_py-1.9.0-py3-none-any.whl", hash = "sha256:340062b1587fc2880892811e2ab5b176d98ef3eee98b3672ff3a3ba1e8ed0f6f", size = 8351, upload-time = "2026-01-02T23:39:30.995Z" }, ] [[package]] name = "starlette" version = "0.52.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "anyio" }, { name = "typing-extensions" }] +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] [[package]] @@ -1933,7 +2043,7 @@ version = "25.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ef/52/9ba0f43b686e7f3ddfeaa78ac3af750292662284b3661e91ad5494f21dbc/structlog-25.5.0.tar.gz", hash = "sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98", size = 1460830, upload-time = "2025-10-27T08:28:23.028Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, + { url = "https://files.pythonhosted.org/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, ] [[package]] @@ -1942,16 +2052,53 @@ version = "1.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, ] +[[package]] +name = "tide" +version = "0.0.1" +source = { editable = "models/tide" } +dependencies = [ + { name = "boto3" }, + { name = "numpy" }, + { name = "pandera", extra = ["polars"] }, + { name = "polars" }, + { name = "prefect" }, + { name = "requests" }, + { name = "structlog" }, + { name = "tinygrad" }, + { name = "tools" }, +] + +[package.dev-dependencies] +dev = [ + { name = "boto3-stubs", extra = ["s3"] }, +] + +[package.metadata] +requires-dist = [ + { name = "boto3", specifier = ">=1.40.74" }, + { name = "numpy", specifier = ">=1.26.4" }, + { name = "pandera", extras = ["polars"], specifier = ">=0.26.0" }, + { name = "polars", specifier = ">=1.29.0" }, + { name = "prefect", specifier = ">=3.0.0,<4.0.0" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "structlog", specifier = ">=25.5.0" }, + { name = "tinygrad", specifier = ">=0.10.3" }, + { name = "tools", editable = "tools" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "boto3-stubs", extras = ["s3"], specifier = ">=1.38.0" }] + [[package]] name = "tinygrad" version = "0.12.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/8c/83bc0895d78d335bd3f5ceadb8a6750c1834f058a0f7e5ee763dc0d626bf/tinygrad-0.12.0.tar.gz", hash = "sha256:299cd53d9452b8689b36c7026c866b73d1e4a862ec53a8ea5bc2a412358c1bbf", size = 1761546, upload-time = "2026-01-12T17:05:09.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/1e/034a535398b1fca9e9a0ece0bc870c122c79c80866eca3ed802bfea7b0df/tinygrad-0.12.0-py3-none-any.whl", hash = "sha256:275c01961e0959579ba99c6ce1ed70a2c2f1a408ed8cfb5c4de8ad1355e66611", size = 1673238, upload-time = "2026-01-12T17:05:06.727Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/034a535398b1fca9e9a0ece0bc870c122c79c80866eca3ed802bfea7b0df/tinygrad-0.12.0-py3-none-any.whl", hash = "sha256:275c01961e0959579ba99c6ce1ed70a2c2f1a408ed8cfb5c4de8ad1355e66611", size = 1673238, upload-time = "2026-01-12T17:05:06.727Z" }, ] [[package]] @@ -1960,7 +2107,7 @@ version = "0.10.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] [[package]] @@ -1968,23 +2115,23 @@ name = "tools" version = "0.0.1" source = { editable = "tools" } dependencies = [ - { name = "boto3" }, - { name = "polars" }, - { name = "prefect" }, - { name = "requests" }, - { name = "structlog" }, + { name = "boto3" }, + { name = "polars" }, + { name = "requests" }, + { name = "structlog" }, ] [package.dev-dependencies] -dev = [{ name = "boto3-stubs", extra = ["s3"] }] +dev = [ + { name = "boto3-stubs", extra = ["s3"] }, +] [package.metadata] requires-dist = [ - { name = "boto3", specifier = ">=1.40.74" }, - { name = "polars", specifier = ">=1.29.0" }, - { name = "prefect", specifier = ">=3.0.0,<4.0.0" }, - { name = "requests", specifier = ">=2.32.5" }, - { name = "structlog", specifier = ">=25.5.0" }, + { name = "boto3", specifier = ">=1.40.74" }, + { name = "polars", specifier = ">=1.29.0" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "structlog", specifier = ">=25.5.0" }, ] [package.metadata.requires-dev] @@ -1994,10 +2141,12 @@ dev = [{ name = "boto3-stubs", extras = ["s3"], specifier = ">=1.38.0" }] name = "typeguard" version = "4.5.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "typing-extensions" }] +dependencies = [ + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/2b/e8/66e25efcc18542d58706ce4e50415710593721aae26e794ab1dec34fb66f/typeguard-4.5.1.tar.gz", hash = "sha256:f6f8ecbbc819c9bc749983cc67c02391e16a9b43b8b27f15dc70ed7c4a007274", size = 80121, upload-time = "2026-02-19T16:09:03.392Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl", hash = "sha256:44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40", size = 36745, upload-time = "2026-02-19T16:09:01.6Z" }, + { url = "https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl", hash = "sha256:44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40", size = 36745, upload-time = "2026-02-19T16:09:01.6Z" }, ] [[package]] @@ -2005,14 +2154,14 @@ name = "typer" version = "0.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, ] [[package]] @@ -2021,7 +2170,7 @@ version = "0.31.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/24/5497a611f32cbaf4b9e1af35f56463e8f02e198ec513b68cb59a63f5a446/types_awscrt-0.31.2.tar.gz", hash = "sha256:dc79705acd24094656b8105b8d799d7e273c8eac37c69137df580cd84beb54f6", size = 18190, upload-time = "2026-02-16T02:33:53.135Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/3d/21a2212b5fcef9e8e9f368403885dc567b7d31e50b2ce393efad3cd83572/types_awscrt-0.31.2-py3-none-any.whl", hash = "sha256:3d6a29c1cca894b191be408f4d985a8e3a14d919785652dd3fa4ee558143e4bf", size = 43340, upload-time = "2026-02-16T02:33:52.109Z" }, + { url = "https://files.pythonhosted.org/packages/ab/3d/21a2212b5fcef9e8e9f368403885dc567b7d31e50b2ce393efad3cd83572/types_awscrt-0.31.2-py3-none-any.whl", hash = "sha256:3d6a29c1cca894b191be408f4d985a8e3a14d919785652dd3fa4ee558143e4bf", size = 43340, upload-time = "2026-02-16T02:33:52.109Z" }, ] [[package]] @@ -2030,7 +2179,7 @@ version = "0.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fe/64/42689150509eb3e6e82b33ee3d89045de1592488842ddf23c56957786d05/types_s3transfer-0.16.0.tar.gz", hash = "sha256:b4636472024c5e2b62278c5b759661efeb52a81851cde5f092f24100b1ecb443", size = 13557, upload-time = "2025-12-08T08:13:09.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/27/e88220fe6274eccd3bdf95d9382918716d312f6f6cef6a46332d1ee2feff/types_s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:1c0cd111ecf6e21437cb410f5cddb631bfb2263b77ad973e79b9c6d0cb24e0ef", size = 19247, upload-time = "2025-12-08T08:13:08.426Z" }, + { url = "https://files.pythonhosted.org/packages/98/27/e88220fe6274eccd3bdf95d9382918716d312f6f6cef6a46332d1ee2feff/types_s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:1c0cd111ecf6e21437cb410f5cddb631bfb2263b77ad973e79b9c6d0cb24e0ef", size = 19247, upload-time = "2025-12-08T08:13:08.426Z" }, ] [[package]] @@ -2039,27 +2188,32 @@ version = "4.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "typing-inspect" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "mypy-extensions" }, { name = "typing-extensions" }] +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, ] [[package]] name = "typing-inspection" version = "0.4.2" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "typing-extensions" }] +dependencies = [ + { name = "typing-extensions" }, +] sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] @@ -2068,17 +2222,19 @@ version = "2025.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] [[package]] name = "tzlocal" version = "5.3.1" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "tzdata", marker = "sys_platform == 'win32'" }] +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, ] [[package]] @@ -2087,7 +2243,7 @@ version = "0.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/02/7c/b5b7d8136f872e3f13b0584e576886de0489d7213a12de6bebf29ff6ebfc/uncalled_for-0.2.0.tar.gz", hash = "sha256:b4f8fdbcec328c5a113807d653e041c5094473dd4afa7c34599ace69ccb7e69f", size = 49488, upload-time = "2026-02-27T17:40:58.137Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/7f/4320d9ce3be404e6310b915c3629fe27bf1e2f438a1a7a3cb0396e32e9a9/uncalled_for-0.2.0-py3-none-any.whl", hash = "sha256:2c0bd338faff5f930918f79e7eb9ff48290df2cb05fcc0b40a7f334e55d4d85f", size = 11351, upload-time = "2026-02-27T17:40:56.804Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7f/4320d9ce3be404e6310b915c3629fe27bf1e2f438a1a7a3cb0396e32e9a9/uncalled_for-0.2.0-py3-none-any.whl", hash = "sha256:2c0bd338faff5f930918f79e7eb9ff48290df2cb05fcc0b40a7f334e55d4d85f", size = 11351, upload-time = "2026-02-27T17:40:56.804Z" }, ] [[package]] @@ -2096,17 +2252,20 @@ version = "2.6.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] name = "uvicorn" version = "0.41.0" source = { registry = "https://pypi.org/simple" } -dependencies = [{ name = "click" }, { name = "h11" }] +dependencies = [ + { name = "click" }, + { name = "h11" }, +] sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, ] [[package]] @@ -2115,16 +2274,16 @@ version = "16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, - { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, - { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, - { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, - { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, - { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, - { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] [[package]] @@ -2133,5 +2292,5 @@ version = "3.23.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ] From c466b94fd8f161193cc7299960bf2e51bd36b14f Mon Sep 17 00:00:00 2001 From: John Forstmeier Date: Fri, 13 Mar 2026 14:27:18 -0400 Subject: [PATCH 2/6] Update infrastructure resource/variable names --- applications/data_manager/src/main.rs | 4 +- applications/data_manager/src/startup.rs | 4 +- .../src/portfolio_manager/server.py | 8 +- infrastructure/__main__.py | 404 +++++++++--------- infrastructure/docker-compose.yaml | 18 +- infrastructure/parameters.py | 10 +- maskfile.md | 4 +- 7 files changed, 229 insertions(+), 223 deletions(-) diff --git a/applications/data_manager/src/main.rs b/applications/data_manager/src/main.rs index adc2f8145..3f9a5e950 100644 --- a/applications/data_manager/src/main.rs +++ b/applications/data_manager/src/main.rs @@ -53,7 +53,7 @@ mod tests { std::env::set_var("MASSIVE_BASE_URL", "http://test"); std::env::set_var("MASSIVE_API_KEY", "test-key"); std::env::set_var("SENTRY_DSN", ""); - std::env::set_var("ENVIRONMENT", "test"); + std::env::set_var("FUND_ENVIRONMENT", "test"); std::env::set_var("RUST_LOG", "data_manager=debug,tower_http=debug"); } @@ -66,7 +66,7 @@ mod tests { std::env::remove_var("MASSIVE_BASE_URL"); std::env::remove_var("MASSIVE_API_KEY"); std::env::remove_var("SENTRY_DSN"); - std::env::remove_var("ENVIRONMENT"); + std::env::remove_var("FUND_ENVIRONMENT"); std::env::remove_var("RUST_LOG"); } } diff --git a/applications/data_manager/src/startup.rs b/applications/data_manager/src/startup.rs index 4e3c0bdd7..a7740db1a 100644 --- a/applications/data_manager/src/startup.rs +++ b/applications/data_manager/src/startup.rs @@ -10,7 +10,7 @@ pub fn initialize_sentry() -> sentry::ClientInitGuard { sentry::ClientOptions { release: sentry::release_name!(), environment: Some( - env::var("ENVIRONMENT") + env::var("FUND_ENVIRONMENT") .unwrap_or_else(|_| "development".to_string()) // Defaults to development so local runs don't require this .into(), ), @@ -153,7 +153,7 @@ mod tests { #[test] #[serial] fn test_initialize_observability_functions() { - let _environment_guard = EnvironmentVariableGuard::set("ENVIRONMENT", "test"); + let _environment_guard = EnvironmentVariableGuard::set("FUND_ENVIRONMENT", "test"); let _sentry_dsn_guard = EnvironmentVariableGuard::set("SENTRY_DSN", ""); let _rust_log_guard = EnvironmentVariableGuard::set("RUST_LOG", "data_manager=debug,tower_http=debug"); diff --git a/applications/portfolio_manager/src/portfolio_manager/server.py b/applications/portfolio_manager/src/portfolio_manager/server.py index a831fcba7..5011be235 100644 --- a/applications/portfolio_manager/src/portfolio_manager/server.py +++ b/applications/portfolio_manager/src/portfolio_manager/server.py @@ -73,9 +73,9 @@ DATAMANAGER_BASE_URL = os.getenv("FUND_DATAMANAGER_BASE_URL", "http://datamanager:8080") HTTP_BAD_REQUEST = 400 _MINIMUM_PAIR_PRICE_ROWS = 30 -EQUITYPRICEMODEL_BASE_URL = os.getenv( - "FUND_EQUITYPRICEMODEL_BASE_URL", - "http://equitypricemodel:8080", +ENSEMBLE_MANAGER_BASE_URL = os.getenv( + "FUND_ENSEMBLE_MANAGER_BASE_URL", + "http://ensemble-manager:8080", ) ALPACA_API_KEY_ID = os.getenv("ALPACA_API_KEY_ID", "") @@ -467,7 +467,7 @@ async def create_portfolio() -> Response: # noqa: PLR0911, PLR0912, PLR0915, C9 async def get_raw_predictions() -> pl.DataFrame: async with httpx.AsyncClient(timeout=60.0) as client: response = await client.post( - url=f"{EQUITYPRICEMODEL_BASE_URL}/predictions", + url=f"{ENSEMBLE_MANAGER_BASE_URL}/predictions", ) response.raise_for_status() return pl.DataFrame(response.json()["data"]) diff --git a/infrastructure/__main__.py b/infrastructure/__main__.py index 4457b9cbe..db33fcd16 100644 --- a/infrastructure/__main__.py +++ b/infrastructure/__main__.py @@ -103,15 +103,17 @@ def serialize_secret_config_object( "trainingNotificationRecipientEmails" ) -datamanager_secret_name = stack_config.require_secret("datamanagerSecretName") -portfoliomanager_secret_name = stack_config.require_secret("portfoliomanagerSecretName") +data_manager_secret_name = stack_config.require_secret("datamanagerSecretName") +portfolio_manager_secret_name = stack_config.require_secret( + "portfoliomanagerSecretName" +) shared_secret_name = stack_config.require_secret("sharedSecretName") -datamanager_secret_values = require_secret_config_object( +data_manager_secret_values = require_secret_config_object( stack_config, "datamanagerSecretValue", ) -portfoliomanager_secret_values = require_secret_config_object( +portfolio_manager_secret_values = require_secret_config_object( stack_config, "portfoliomanagerSecretValue", ) @@ -153,16 +155,16 @@ def serialize_secret_config_object( ":oidc-provider/token.actions.githubusercontent.com", ) -datamanager_secret = aws.secretsmanager.Secret( - "datamanager_secret", - name=datamanager_secret_name, +data_manager_secret = aws.secretsmanager.Secret( + "data_manager_secret", + name=data_manager_secret_name, recovery_window_in_days=0, tags=tags, ) -portfoliomanager_secret = aws.secretsmanager.Secret( - "portfoliomanager_secret", - name=portfoliomanager_secret_name, +portfolio_manager_secret = aws.secretsmanager.Secret( + "portfolio_manager_secret", + name=portfolio_manager_secret_name, recovery_window_in_days=0, tags=tags, ) @@ -175,9 +177,9 @@ def serialize_secret_config_object( ) aws.secretsmanager.SecretVersion( - "datamanager_secret_version", - secret_id=datamanager_secret.id, - secret_string=datamanager_secret_values.apply( + "data_manager_secret_version", + secret_id=data_manager_secret.id, + secret_string=data_manager_secret_values.apply( lambda values: serialize_secret_config_object( values, "datamanagerSecretValue", @@ -187,9 +189,9 @@ def serialize_secret_config_object( ) aws.secretsmanager.SecretVersion( - "portfoliomanager_secret_version", - secret_id=portfoliomanager_secret.id, - secret_string=portfoliomanager_secret_values.apply( + "portfolio_manager_secret_version", + secret_id=portfolio_manager_secret.id, + secret_string=portfolio_manager_secret_values.apply( lambda values: serialize_secret_config_object( values, "portfoliomanagerSecretValue", @@ -254,10 +256,14 @@ def serialize_secret_config_object( ) # S3 Data Bucket for storing equity bars, predictions, portfolios -data_bucket = aws.s3.Bucket( +# alias: migrated from aws:s3/bucket:Bucket to aws:s3/bucketV2:BucketV2 +data_bucket = aws.s3.BucketV2( "data_bucket", bucket=pulumi.Output.concat("fund-data-", random_suffix), - opts=pulumi.ResourceOptions(retain_on_delete=True), + opts=pulumi.ResourceOptions( + retain_on_delete=True, + aliases=[pulumi.Alias(type_="aws:s3/bucket:Bucket")], + ), tags=tags, ) @@ -294,10 +300,14 @@ def serialize_secret_config_object( ) # S3 Model Artifacts Bucket for storing trained model weights and checkpoints -model_artifacts_bucket = aws.s3.Bucket( +# alias: migrated from aws:s3/bucket:Bucket to aws:s3/bucketV2:BucketV2 +model_artifacts_bucket = aws.s3.BucketV2( "model_artifacts_bucket", bucket=pulumi.Output.concat("fund-model-artifacts-", random_suffix), - opts=pulumi.ResourceOptions(retain_on_delete=True), + opts=pulumi.ResourceOptions( + retain_on_delete=True, + aliases=[pulumi.Alias(type_="aws:s3/bucket:Bucket")], + ), tags=tags, ) @@ -337,9 +347,9 @@ def serialize_secret_config_object( # force_delete allows repositories containing images to be deleted on stack teardown. # If image rebuild and push times become prohibitive on daily down/up cycles, switch to # retain_on_delete=True and add pulumi import statements to the maskfile up command. -datamanager_repository = aws.ecr.Repository( - "datamanager_repository", - name="fund/datamanager-server", +data_manager_repository = aws.ecr.Repository( + "data_manager_repository", + name="fund/data-manager-server", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -348,9 +358,9 @@ def serialize_secret_config_object( tags=tags, ) -portfoliomanager_repository = aws.ecr.Repository( - "portfoliomanager_repository", - name="fund/portfoliomanager-server", +portfolio_manager_repository = aws.ecr.Repository( + "portfolio_manager_repository", + name="fund/portfolio-manager-server", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -359,9 +369,9 @@ def serialize_secret_config_object( tags=tags, ) -equitypricemodel_repository = aws.ecr.Repository( - "equitypricemodel_repository", - name="fund/equitypricemodel-server", +ensemble_manager_repository = aws.ecr.Repository( + "ensemble_manager_repository", + name="fund/ensemble-manager-server", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -370,9 +380,9 @@ def serialize_secret_config_object( tags=tags, ) -equitypricemodel_trainer_repository = aws.ecr.Repository( - "equitypricemodel_trainer_repository", - name="fund/equitypricemodel-trainer", +tide_trainer_repository = aws.ecr.Repository( + "tide_trainer_repository", + name="fund/tide-trainer", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -381,9 +391,9 @@ def serialize_secret_config_object( tags=tags, ) -prefect_server_repository = aws.ecr.Repository( - "prefect_server_repository", - name="fund/prefect-server", +training_server_repository = aws.ecr.Repository( + "training_server_repository", + name="fund/training-server", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -392,9 +402,9 @@ def serialize_secret_config_object( tags=tags, ) -prefect_worker_repository = aws.ecr.Repository( - "prefect_worker_repository", - name="fund/prefect-worker", +training_worker_repository = aws.ecr.Repository( + "training_worker_repository", + name="fund/training-worker", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -405,24 +415,22 @@ def serialize_secret_config_object( # Generate image URIs - these will be used in task definitions # For initial deployment, use a placeholder that will be updated when images are pushed -datamanager_image_uri = datamanager_repository.repository_url.apply( +data_manager_image_uri = data_manager_repository.repository_url.apply( lambda url: f"{url}:latest" ) -portfoliomanager_image_uri = portfoliomanager_repository.repository_url.apply( +portfolio_manager_image_uri = portfolio_manager_repository.repository_url.apply( lambda url: f"{url}:latest" ) -equitypricemodel_image_uri = equitypricemodel_repository.repository_url.apply( +ensemble_manager_image_uri = ensemble_manager_repository.repository_url.apply( lambda url: f"{url}:latest" ) -equitypricemodel_trainer_image_uri = ( - equitypricemodel_trainer_repository.repository_url.apply( - lambda url: f"{url}:latest" - ) +tide_trainer_image_uri = tide_trainer_repository.repository_url.apply( + lambda url: f"{url}:latest" ) -prefect_server_image_uri = prefect_server_repository.repository_url.apply( +training_server_image_uri = training_server_repository.repository_url.apply( lambda url: f"{url}:latest" ) -prefect_worker_image_uri = prefect_worker_repository.repository_url.apply( +training_worker_image_uri = training_worker_repository.repository_url.apply( lambda url: f"{url}:latest" ) @@ -745,9 +753,9 @@ def serialize_secret_config_object( tags=tags, ) -datamanager_tg = aws.lb.TargetGroup( - "datamanager_tg", - name="fund-datamanager", +data_manager_tg = aws.lb.TargetGroup( + "data_manager_tg", + name="fund-data-manager", port=8080, protocol="HTTP", vpc_id=vpc.id, @@ -762,9 +770,9 @@ def serialize_secret_config_object( tags=tags, ) -portfoliomanager_tg = aws.lb.TargetGroup( - "portfoliomanager_tg", - name="fund-portfoliomanager", +portfolio_manager_tg = aws.lb.TargetGroup( + "portfolio_manager_tg", + name="fund-portfolio-manager", port=8080, protocol="HTTP", vpc_id=vpc.id, @@ -779,9 +787,9 @@ def serialize_secret_config_object( tags=tags, ) -equitypricemodel_tg = aws.lb.TargetGroup( - "equitypricemodel_tg", - name="fund-equitypricemodel", +ensemble_manager_tg = aws.lb.TargetGroup( + "ensemble_manager_tg", + name="fund-ensemble-manager", port=8080, protocol="HTTP", vpc_id=vpc.id, @@ -796,9 +804,9 @@ def serialize_secret_config_object( tags=tags, ) -prefect_tg = aws.lb.TargetGroup( - "prefect_tg", - name="fund-prefect", +training_tg = aws.lb.TargetGroup( + "training_tg", + name="fund-training", port=4200, protocol="HTTP", vpc_id=vpc.id, @@ -828,7 +836,7 @@ def serialize_secret_config_object( default_actions=[ aws.lb.ListenerDefaultActionArgs( type="forward", - target_group_arn=prefect_tg.arn, + target_group_arn=training_tg.arn, ) ], tags=tags, @@ -842,7 +850,7 @@ def serialize_secret_config_object( default_actions=[ aws.lb.ListenerDefaultActionArgs( type="forward", - target_group_arn=prefect_tg.arn, + target_group_arn=training_tg.arn, ) ], tags=tags, @@ -913,13 +921,13 @@ def serialize_secret_config_object( # Listener Rules for routing attached to primary listener aws.lb.ListenerRule( - "portfoliomanager_rule", + "portfolio_manager_rule", listener_arn=alb_listener.arn, priority=200, # Ensures that the more specific data manager paths take precedence actions=[ aws.lb.ListenerRuleActionArgs( type="forward", - target_group_arn=portfoliomanager_tg.arn, + target_group_arn=portfolio_manager_tg.arn, ) ], conditions=[ @@ -933,13 +941,13 @@ def serialize_secret_config_object( ) aws.lb.ListenerRule( - "datamanager_rule", + "data_manager_rule", listener_arn=alb_listener.arn, priority=100, actions=[ aws.lb.ListenerRuleActionArgs( type="forward", - target_group_arn=datamanager_tg.arn, + target_group_arn=data_manager_tg.arn, ) ], conditions=[ @@ -958,13 +966,13 @@ def serialize_secret_config_object( ) aws.lb.ListenerRule( - "equitypricemodel_rule", + "ensemble_manager_rule", listener_arn=alb_listener.arn, priority=150, actions=[ aws.lb.ListenerRuleActionArgs( type="forward", - target_group_arn=equitypricemodel_tg.arn, + target_group_arn=ensemble_manager_tg.arn, ) ], conditions=[ @@ -991,8 +999,8 @@ def serialize_secret_config_object( "Least-privilege policy for GitHub Actions infrastructure deployments." ), policy=pulumi.Output.all( - datamanager_secret_name, - portfoliomanager_secret_name, + data_manager_secret_name, + portfolio_manager_secret_name, shared_secret_name, github_oidc_provider_arn, ).apply( @@ -1387,8 +1395,8 @@ def serialize_secret_config_object( name="fund-ecs-execution-role-secrets-policy", role=execution_role.id, policy=pulumi.Output.all( - datamanager_secret.arn, - portfoliomanager_secret.arn, + data_manager_secret.arn, + portfolio_manager_secret.arn, shared_secret.arn, ).apply( lambda args: json.dumps( @@ -1481,7 +1489,7 @@ def serialize_secret_config_object( training_notification_sender_email_parameter = aws.ssm.Parameter( "training_notification_sender_email_parameter", - name="/fund/prefect/training_notification_sender_email", + name="/fund/training/training_notification_sender_email", type="SecureString", value=training_notification_sender_email, tags=tags, @@ -1489,7 +1497,7 @@ def serialize_secret_config_object( training_notification_recipients_parameter = aws.ssm.Parameter( "training_notification_recipients_parameter", - name="/fund/prefect/training_notification_recipients", + name="/fund/training/training_notification_recipients", type="SecureString", value=training_notification_recipient_emails, tags=tags, @@ -1678,25 +1686,25 @@ def serialize_secret_config_object( ) # Prefect Server Log Group -prefect_server_log_group = aws.cloudwatch.LogGroup( - "prefect_server_logs", - name="/ecs/fund/prefect-server", +training_server_log_group = aws.cloudwatch.LogGroup( + "training_server_logs", + name="/ecs/fund/training-server", retention_in_days=7, tags=tags, ) # Prefect Worker Log Group -prefect_worker_log_group = aws.cloudwatch.LogGroup( - "prefect_worker_logs", - name="/ecs/fund/prefect-worker", +training_worker_log_group = aws.cloudwatch.LogGroup( + "training_worker_logs", + name="/ecs/fund/training-worker", retention_in_days=7, tags=tags, ) # Prefect Server Task Definition -prefect_server_task_definition = aws.ecs.TaskDefinition( - "prefect_server_task", - family="prefect-server", +training_server_task_definition = aws.ecs.TaskDefinition( + "training_server_task", + family="training-server", cpu="512", memory="1024", network_mode="awsvpc", @@ -1704,16 +1712,16 @@ def serialize_secret_config_object( execution_role_arn=execution_role.arn, task_role_arn=task_role.arn, container_definitions=pulumi.Output.all( - prefect_server_log_group.name, + training_server_log_group.name, prefect_database.endpoint, prefect_database.master_user_secrets[0]["secret_arn"], - prefect_server_image_uri, + training_server_image_uri, alb.dns_name, ).apply( lambda args: json.dumps( [ { - "name": "prefect-server", + "name": "training-server", "image": args[3], # Inline bash/python constructs the database URL at runtime # because the password comes from Secrets Manager and must be @@ -1754,7 +1762,7 @@ def serialize_secret_config_object( "options": { "awslogs-group": args[0], "awslogs-region": region, - "awslogs-stream-prefix": "prefect-server", + "awslogs-stream-prefix": "training-server", }, }, "essential": True, @@ -1767,9 +1775,9 @@ def serialize_secret_config_object( ) # Prefect Server Service Discovery -prefect_server_sd_service = aws.servicediscovery.Service( - "prefect_server_sd", - name="prefect-server", +training_server_sd_service = aws.servicediscovery.Service( + "training_server_sd", + name="training-server", dns_config=aws.servicediscovery.ServiceDnsConfigArgs( namespace_id=service_discovery_namespace.id, dns_records=[ @@ -1780,11 +1788,11 @@ def serialize_secret_config_object( ) # Prefect Server ECS Service -prefect_server_service = aws.ecs.Service( - "prefect_server_service", - name="fund-prefect-server", +training_server_service = aws.ecs.Service( + "training_server_service", + name="fund-training-server", cluster=cluster.arn, - task_definition=prefect_server_task_definition.arn, + task_definition=training_server_task_definition.arn, desired_count=1, launch_type="FARGATE", network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( @@ -1794,13 +1802,13 @@ def serialize_secret_config_object( ), load_balancers=[ aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=prefect_tg.arn, - container_name="prefect-server", + target_group_arn=training_tg.arn, + container_name="training-server", container_port=4200, ) ], service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=prefect_server_sd_service.arn + registry_arn=training_server_sd_service.arn ), opts=pulumi.ResourceOptions( depends_on=[prefect_database, prefect_redis, prefect_listener], @@ -1809,9 +1817,9 @@ def serialize_secret_config_object( ) # Prefect Worker Task Definition -prefect_worker_task_definition = aws.ecs.TaskDefinition( - "prefect_worker_task", - family="prefect-worker", +training_worker_task_definition = aws.ecs.TaskDefinition( + "training_worker_task", + family="training-worker", cpu="4096", memory="8192", network_mode="awsvpc", @@ -1819,23 +1827,23 @@ def serialize_secret_config_object( execution_role_arn=execution_role.arn, task_role_arn=task_role.arn, container_definitions=pulumi.Output.all( - prefect_worker_log_group.name, + training_worker_log_group.name, service_discovery_namespace.name, data_bucket.bucket, model_artifacts_bucket.bucket, - prefect_worker_image_uri, + training_worker_image_uri, training_notification_sender_email_parameter.arn, training_notification_recipients_parameter.arn, ).apply( lambda args: json.dumps( [ { - "name": "prefect-worker", + "name": "training-worker", "image": args[4], "environment": [ { "name": "PREFECT_API_URL", - "value": f"http://prefect-server.{args[1]}:4200/api", + "value": f"http://training-server.{args[1]}:4200/api", }, { "name": "AWS_S3_DATA_BUCKET_NAME", @@ -1847,20 +1855,20 @@ def serialize_secret_config_object( }, { "name": "FUND_DATAMANAGER_BASE_URL", - "value": f"http://datamanager.{args[1]}:8080", + "value": f"http://data-manager.{args[1]}:8080", }, { - "name": "LOOKBACK_DAYS", + "name": "FUND_LOOKBACK_DAYS", "value": "365", }, ], "secrets": [ { - "name": "TRAINING_NOTIFICATION_SENDER_EMAIL", + "name": "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL", "valueFrom": args[5], }, { - "name": "TRAINING_NOTIFICATION_RECIPIENT_EMAILS", + "name": "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS", "valueFrom": args[6], }, ], @@ -1869,7 +1877,7 @@ def serialize_secret_config_object( "options": { "awslogs-group": args[0], "awslogs-region": region, - "awslogs-stream-prefix": "prefect-worker", + "awslogs-stream-prefix": "training-worker", }, }, "essential": True, @@ -1882,11 +1890,11 @@ def serialize_secret_config_object( ) # Prefect Worker ECS Service -prefect_worker_service = aws.ecs.Service( - "prefect_worker_service", - name="fund-prefect-worker", +training_worker_service = aws.ecs.Service( + "training_worker_service", + name="fund-training-worker", cluster=cluster.arn, - task_definition=prefect_worker_task_definition.arn, + task_definition=training_worker_task_definition.arn, desired_count=1, launch_type="FARGATE", network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( @@ -1895,35 +1903,35 @@ def serialize_secret_config_object( assign_public_ip=False, ), opts=pulumi.ResourceOptions( - depends_on=[prefect_server_service], + depends_on=[training_server_service], ), tags=tags, ) -datamanager_log_group = aws.cloudwatch.LogGroup( - "datamanager_logs", - name="/ecs/fund/datamanager", +data_manager_log_group = aws.cloudwatch.LogGroup( + "data_manager_logs", + name="/ecs/fund/data-manager", retention_in_days=7, tags=tags, ) -portfoliomanager_log_group = aws.cloudwatch.LogGroup( - "portfoliomanager_logs", - name="/ecs/fund/portfoliomanager", +portfolio_manager_log_group = aws.cloudwatch.LogGroup( + "portfolio_manager_logs", + name="/ecs/fund/portfolio-manager", retention_in_days=7, tags=tags, ) -equitypricemodel_log_group = aws.cloudwatch.LogGroup( - "equitypricemodel_logs", - name="/ecs/fund/equitypricemodel", +ensemble_manager_log_group = aws.cloudwatch.LogGroup( + "ensemble_manager_logs", + name="/ecs/fund/ensemble-manager", retention_in_days=7, tags=tags, ) -datamanager_task_definition = aws.ecs.TaskDefinition( - "datamanager_task", - family="datamanager", +data_manager_task_definition = aws.ecs.TaskDefinition( + "data_manager_task", + family="data-manager", cpu="256", memory="512", network_mode="awsvpc", @@ -1931,16 +1939,16 @@ def serialize_secret_config_object( execution_role_arn=execution_role.arn, task_role_arn=task_role.arn, container_definitions=pulumi.Output.all( - datamanager_log_group.name, - datamanager_image_uri, - datamanager_secret.arn, + data_manager_log_group.name, + data_manager_image_uri, + data_manager_secret.arn, shared_secret.arn, data_bucket.bucket, ).apply( lambda args: json.dumps( [ { - "name": "datamanager", + "name": "data-manager", "image": args[1], "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], "environment": [ @@ -1953,12 +1961,12 @@ def serialize_secret_config_object( "value": args[4], }, { - "name": "ENVIRONMENT", + "name": "FUND_ENVIRONMENT", "value": "production", }, { "name": "RUST_LOG", - "value": "datamanager=info,tower_http=info", + "value": "data_manager=info,tower_http=info", }, ], "secrets": [ @@ -1976,7 +1984,7 @@ def serialize_secret_config_object( "options": { "awslogs-group": args[0], "awslogs-region": region, - "awslogs-stream-prefix": "datamanager", + "awslogs-stream-prefix": "data-manager", }, }, "essential": True, @@ -1988,9 +1996,9 @@ def serialize_secret_config_object( tags=tags, ) -portfoliomanager_task_definition = aws.ecs.TaskDefinition( - "portfoliomanager_task", - family="portfoliomanager", +portfolio_manager_task_definition = aws.ecs.TaskDefinition( + "portfolio_manager_task", + family="portfolio-manager", cpu="256", memory="512", network_mode="awsvpc", @@ -1998,30 +2006,30 @@ def serialize_secret_config_object( execution_role_arn=execution_role.arn, task_role_arn=task_role.arn, container_definitions=pulumi.Output.all( - portfoliomanager_log_group.name, + portfolio_manager_log_group.name, service_discovery_namespace.name, - portfoliomanager_image_uri, - portfoliomanager_secret.arn, + portfolio_manager_image_uri, + portfolio_manager_secret.arn, shared_secret.arn, parameters.uncertainty_threshold.value, ).apply( lambda args: json.dumps( [ { - "name": "portfoliomanager", + "name": "portfolio-manager", "image": args[2], "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], "environment": [ { "name": "FUND_DATAMANAGER_BASE_URL", - "value": f"http://datamanager.{args[1]}:8080", + "value": f"http://data-manager.{args[1]}:8080", }, { - "name": "FUND_EQUITYPRICEMODEL_BASE_URL", - "value": f"http://equitypricemodel.{args[1]}:8080", + "name": "FUND_ENSEMBLE_MANAGER_BASE_URL", + "value": f"http://ensemble-manager.{args[1]}:8080", }, { - "name": "ENVIRONMENT", + "name": "FUND_ENVIRONMENT", "value": "production", }, { @@ -2052,7 +2060,7 @@ def serialize_secret_config_object( "options": { "awslogs-group": args[0], "awslogs-region": region, - "awslogs-stream-prefix": "portfoliomanager", + "awslogs-stream-prefix": "portfolio-manager", }, }, "essential": True, @@ -2064,9 +2072,9 @@ def serialize_secret_config_object( tags=tags, ) -equitypricemodel_task_definition = aws.ecs.TaskDefinition( - "equitypricemodel_task", - family="equitypricemodel", +ensemble_manager_task_definition = aws.ecs.TaskDefinition( + "ensemble_manager_task", + family="ensemble-manager", cpu="256", memory="512", network_mode="awsvpc", @@ -2074,29 +2082,29 @@ def serialize_secret_config_object( execution_role_arn=execution_role.arn, task_role_arn=task_role.arn, container_definitions=pulumi.Output.all( - equitypricemodel_log_group.name, + ensemble_manager_log_group.name, service_discovery_namespace.name, - equitypricemodel_image_uri, + ensemble_manager_image_uri, model_artifacts_bucket.bucket, shared_secret.arn, ).apply( lambda args: json.dumps( [ { - "name": "equitypricemodel", + "name": "ensemble-manager", "image": args[2], "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], "environment": [ { "name": "FUND_DATAMANAGER_BASE_URL", - "value": f"http://datamanager.{args[1]}:8080", + "value": f"http://data-manager.{args[1]}:8080", }, { "name": "AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME", "value": args[3], }, { - "name": "ENVIRONMENT", + "name": "FUND_ENVIRONMENT", "value": "production", }, { @@ -2115,7 +2123,7 @@ def serialize_secret_config_object( "options": { "awslogs-group": args[0], "awslogs-region": region, - "awslogs-stream-prefix": "equitypricemodel", + "awslogs-stream-prefix": "ensemble-manager", }, }, "essential": True, @@ -2127,9 +2135,9 @@ def serialize_secret_config_object( tags=tags, ) -datamanager_sd_service = aws.servicediscovery.Service( - "datamanager_sd", - name="datamanager", +data_manager_sd_service = aws.servicediscovery.Service( + "data_manager_sd", + name="data-manager", dns_config=aws.servicediscovery.ServiceDnsConfigArgs( namespace_id=service_discovery_namespace.id, dns_records=[ @@ -2139,9 +2147,9 @@ def serialize_secret_config_object( tags=tags, ) -portfoliomanager_sd_service = aws.servicediscovery.Service( - "portfoliomanager_sd", - name="portfoliomanager", +portfolio_manager_sd_service = aws.servicediscovery.Service( + "portfolio_manager_sd", + name="portfolio-manager", dns_config=aws.servicediscovery.ServiceDnsConfigArgs( namespace_id=service_discovery_namespace.id, dns_records=[ @@ -2151,9 +2159,9 @@ def serialize_secret_config_object( tags=tags, ) -equitypricemodel_sd_service = aws.servicediscovery.Service( - "equitypricemodel_sd", - name="equitypricemodel", +ensemble_manager_sd_service = aws.servicediscovery.Service( + "ensemble_manager_sd", + name="ensemble-manager", dns_config=aws.servicediscovery.ServiceDnsConfigArgs( namespace_id=service_discovery_namespace.id, dns_records=[ @@ -2163,11 +2171,11 @@ def serialize_secret_config_object( tags=tags, ) -datamanager_service = aws.ecs.Service( - "datamanager_service", - name="fund-datamanager", +data_manager_service = aws.ecs.Service( + "data_manager_service", + name="fund-data-manager", cluster=cluster.arn, - task_definition=datamanager_task_definition.arn, + task_definition=data_manager_task_definition.arn, desired_count=1, launch_type="FARGATE", network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( @@ -2177,23 +2185,23 @@ def serialize_secret_config_object( ), load_balancers=[ aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=datamanager_tg.arn, - container_name="datamanager", + target_group_arn=data_manager_tg.arn, + container_name="data-manager", container_port=8080, ) ], service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=datamanager_sd_service.arn + registry_arn=data_manager_sd_service.arn ), opts=pulumi.ResourceOptions(depends_on=[alb_listener]), tags=tags, ) -portfoliomanager_service = aws.ecs.Service( - "portfoliomanager_service", - name="fund-portfoliomanager", +portfolio_manager_service = aws.ecs.Service( + "portfolio_manager_service", + name="fund-portfolio-manager", cluster=cluster.arn, - task_definition=portfoliomanager_task_definition.arn, + task_definition=portfolio_manager_task_definition.arn, desired_count=1, launch_type="FARGATE", network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( @@ -2203,23 +2211,23 @@ def serialize_secret_config_object( ), load_balancers=[ aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=portfoliomanager_tg.arn, - container_name="portfoliomanager", + target_group_arn=portfolio_manager_tg.arn, + container_name="portfolio-manager", container_port=8080, ) ], service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=portfoliomanager_sd_service.arn + registry_arn=portfolio_manager_sd_service.arn ), - opts=pulumi.ResourceOptions(depends_on=[alb_listener, datamanager_service]), + opts=pulumi.ResourceOptions(depends_on=[alb_listener, data_manager_service]), tags=tags, ) -equitypricemodel_service = aws.ecs.Service( - "equitypricemodel_service", - name="fund-equitypricemodel", +ensemble_manager_service = aws.ecs.Service( + "ensemble_manager_service", + name="fund-ensemble-manager", cluster=cluster.arn, - task_definition=equitypricemodel_task_definition.arn, + task_definition=ensemble_manager_task_definition.arn, desired_count=1, launch_type="FARGATE", network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( @@ -2229,15 +2237,15 @@ def serialize_secret_config_object( ), load_balancers=[ aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=equitypricemodel_tg.arn, - container_name="equitypricemodel", + target_group_arn=ensemble_manager_tg.arn, + container_name="ensemble-manager", container_port=8080, ) ], service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=equitypricemodel_sd_service.arn + registry_arn=ensemble_manager_sd_service.arn ), - opts=pulumi.ResourceOptions(depends_on=[alb_listener, datamanager_service]), + opts=pulumi.ResourceOptions(depends_on=[alb_listener, data_manager_service]), tags=tags, ) @@ -2261,15 +2269,15 @@ def serialize_secret_config_object( pulumi.export("aws_alb_dns_name", alb.dns_name) pulumi.export("aws_alb_url", pulumi.Output.concat(protocol, alb.dns_name)) pulumi.export("aws_service_discovery_namespace", service_discovery_namespace.name) -pulumi.export("aws_ecr_datamanager_image", datamanager_image_uri) -pulumi.export("aws_ecr_portfoliomanager_image", portfoliomanager_image_uri) -pulumi.export("aws_ecr_equitypricemodel_image", equitypricemodel_image_uri) -pulumi.export("aws_ecr_datamanager_repository", datamanager_repository.repository_url) +pulumi.export("aws_ecr_data_manager_image", data_manager_image_uri) +pulumi.export("aws_ecr_portfolio_manager_image", portfolio_manager_image_uri) +pulumi.export("aws_ecr_ensemble_manager_image", ensemble_manager_image_uri) +pulumi.export("aws_ecr_data_manager_repository", data_manager_repository.repository_url) pulumi.export( - "aws_ecr_portfoliomanager_repository", portfoliomanager_repository.repository_url + "aws_ecr_portfolio_manager_repository", portfolio_manager_repository.repository_url ) pulumi.export( - "aws_ecr_equitypricemodel_repository", equitypricemodel_repository.repository_url + "aws_ecr_ensemble_manager_repository", ensemble_manager_repository.repository_url ) pulumi.export("aws_s3_data_bucket_name", pulumi.Output.unsecret(data_bucket.bucket)) pulumi.export( @@ -2277,20 +2285,18 @@ def serialize_secret_config_object( pulumi.Output.unsecret(model_artifacts_bucket.bucket), ) pulumi.export( - "aws_ecr_equitypricemodel_trainer_repository", - equitypricemodel_trainer_repository.repository_url, -) -pulumi.export( - "aws_ecr_equitypricemodel_trainer_image", equitypricemodel_trainer_image_uri + "aws_ecr_tide_trainer_repository", + tide_trainer_repository.repository_url, ) +pulumi.export("aws_ecr_tide_trainer_image", tide_trainer_image_uri) pulumi.export( - "aws_ecr_prefect_worker_repository", prefect_worker_repository.repository_url + "aws_ecr_training_worker_repository", training_worker_repository.repository_url ) -pulumi.export("aws_ecr_prefect_worker_image", prefect_worker_image_uri) +pulumi.export("aws_ecr_training_worker_image", training_worker_image_uri) pulumi.export( "prefect_api_url", pulumi.Output.concat( - "http://prefect-server.", service_discovery_namespace.name, ":4200/api" + "http://training-server.", service_discovery_namespace.name, ":4200/api" ), ) prefect_ui_url = ( diff --git a/infrastructure/docker-compose.yaml b/infrastructure/docker-compose.yaml index ce64a43d1..bf11175f1 100644 --- a/infrastructure/docker-compose.yaml +++ b/infrastructure/docker-compose.yaml @@ -1,7 +1,7 @@ --- version: "3.9" services: - datamanager: + data-manager: image: ${AWS_ECR_DATA_MANAGER_SERVER_IMAGE_ARN} ports: - 8080:8080 @@ -16,18 +16,18 @@ services: interval: 30s timeout: 5s retries: 3 - portfoliomanager: + portfolio-manager: image: ${AWS_ECR_PORTFOLIO_MANAGER_SERVER_IMAGE_ARN} ports: - 8081:8080 environment: - - DATAMANAGER_BASE_URL=http://datamanager:8080 - - EQUITYPRICEMODEL_BASE_URL=http://equitypricemodel:8080 + - FUND_DATAMANAGER_BASE_URL=http://data-manager:8080 + - FUND_ENSEMBLE_MANAGER_BASE_URL=http://ensemble-manager:8080 networks: - app-network depends_on: - - datamanager - - equitypricemodel + - data-manager + - ensemble-manager healthcheck: test: - CMD @@ -37,17 +37,17 @@ services: interval: 30s timeout: 5s retries: 3 - equitypricemodel: + ensemble-manager: image: ${AWS_ECR_EQUITY_PRICE_MODEL_SERVER_IMAGE_ARN} ports: - 8082:8080 environment: - - DATAMANAGER_BASE_URL=http://datamanager:8080 + - FUND_DATAMANAGER_BASE_URL=http://data-manager:8080 - DISABLE_DISK_CACHE=1 networks: - app-network depends_on: - - datamanager + - data-manager healthcheck: test: - CMD diff --git a/infrastructure/parameters.py b/infrastructure/parameters.py index 1893319cb..ae65aa1f8 100644 --- a/infrastructure/parameters.py +++ b/infrastructure/parameters.py @@ -16,17 +16,17 @@ # Portfolio Manager Configuration uncertainty_threshold = aws.ssm.Parameter( "ssm_uncertainty_threshold", - name="/fund/portfoliomanager/uncertainty_threshold", + name="/fund/portfolio-manager/uncertainty_threshold", type="String", value="1.5", description="Maximum inter-quartile range for predictions to be considered valid", tags=tags, ) -# Equity Price Model Configuration -equitypricemodel_model_version = aws.ssm.Parameter( - "ssm_equitypricemodel_model_version", - name="/fund/equitypricemodel/model_version", +# Ensemble Manager Configuration +ensemble_manager_model_version = aws.ssm.Parameter( + "ssm_ensemble_manager_model_version", + name="/fund/ensemble-manager/model_version", type="String", value="latest", description=( diff --git a/maskfile.md b/maskfile.md index 176cc81ad..7f2ba649d 100644 --- a/maskfile.md +++ b/maskfile.md @@ -208,12 +208,12 @@ if [ -n "$GITHUB_POLICY_ARN" ]; then pulumi import --yes --generate-code=false aws:iam/policy:Policy github_actions_infrastructure_policy "$GITHUB_POLICY_ARN" 2>/dev/null || true fi -pulumi import --yes --generate-code=false aws:s3/bucket:Bucket data_bucket "fund-data-${RANDOM_SUFFIX}" 2>/dev/null || true +pulumi import --yes --generate-code=false aws:s3/bucketV2:BucketV2 data_bucket "fund-data-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketServerSideEncryptionConfiguration:BucketServerSideEncryptionConfiguration data_bucket_encryption "fund-data-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketPublicAccessBlock:BucketPublicAccessBlock data_bucket_public_access_block "fund-data-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketVersioning:BucketVersioning data_bucket_versioning "fund-data-${RANDOM_SUFFIX}" 2>/dev/null || true -pulumi import --yes --generate-code=false aws:s3/bucket:Bucket model_artifacts_bucket "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true +pulumi import --yes --generate-code=false aws:s3/bucketV2:BucketV2 model_artifacts_bucket "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketServerSideEncryptionConfiguration:BucketServerSideEncryptionConfiguration model_artifacts_bucket_encryption "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketPublicAccessBlock:BucketPublicAccessBlock model_artifacts_bucket_public_access_block "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketVersioning:BucketVersioning model_artifacts_bucket_versioning "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true From f797fcb5b16eddae8c91cf6c5713eb6a1d2a9572 Mon Sep 17 00:00:00 2001 From: John Forstmeier Date: Fri, 13 Mar 2026 15:27:24 -0400 Subject: [PATCH 3/6] Refactor Mask commands --- infrastructure/__main__.py | 8 +-- maskfile.md | 123 ++++++++----------------------------- tools/Dockerfile | 4 +- 3 files changed, 30 insertions(+), 105 deletions(-) diff --git a/infrastructure/__main__.py b/infrastructure/__main__.py index db33fcd16..f06b16d5c 100644 --- a/infrastructure/__main__.py +++ b/infrastructure/__main__.py @@ -2294,18 +2294,18 @@ def serialize_secret_config_object( ) pulumi.export("aws_ecr_training_worker_image", training_worker_image_uri) pulumi.export( - "prefect_api_url", + "training_api_url", pulumi.Output.concat( "http://training-server.", service_discovery_namespace.name, ":4200/api" ), ) -prefect_ui_url = ( +training_ui_url = ( pulumi.Output.concat("https://", alb.dns_name, ":4200") if acm_certificate_arn else pulumi.Output.from_input("TLS certificate not configured") ) -pulumi.export("prefect_ui_url", prefect_ui_url) -pulumi.export("prefect_ui_tls_enabled", bool(acm_certificate_arn)) +pulumi.export("training_ui_url", training_ui_url) +pulumi.export("training_ui_tls_enabled", bool(acm_certificate_arn)) pulumi.export( "aws_iam_github_actions_infrastructure_role_arn", github_actions_infrastructure_role.arn, diff --git a/maskfile.md b/maskfile.md index 7f2ba649d..741766436 100644 --- a/maskfile.md +++ b/maskfile.md @@ -68,10 +68,15 @@ if [ -z "$aws_region" ]; then exit 1 fi +if [ "${application_name}" = "training" ]; then + dockerfile="tools/Dockerfile" +else + dockerfile="applications/${application_name}/Dockerfile" +fi image_reference="${aws_account_id}.dkr.ecr.${aws_region}.amazonaws.com/fund/${application_name}-${stage_name}" cache_reference="${image_reference}:buildcache" -# Use GHA backend for caching when running in GitHub Actions +# Use GHA backend for caching when running in GitHub Actions if [ -n "${GITHUB_ACTIONS:-}" ]; then scope="${application_name}-${stage_name}" echo "Running in GitHub Actions - using hybrid cache (gha + registry) with scope: ${scope}" @@ -99,7 +104,7 @@ echo "Building with caching (will continue if cache doesn't exist)" docker buildx build \ --platform linux/amd64 \ --target ${stage_name} \ - --file applications/${application_name}/Dockerfile \ + --file ${dockerfile} \ --tag ${image_reference}:latest \ ${cache_from_arguments} \ ${cache_to_arguments} \ @@ -218,7 +223,7 @@ pulumi import --yes --generate-code=false aws:s3/bucketServerSideEncryptionConfi pulumi import --yes --generate-code=false aws:s3/bucketPublicAccessBlock:BucketPublicAccessBlock model_artifacts_bucket_public_access_block "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketVersioning:BucketVersioning model_artifacts_bucket_versioning "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true -pulumi import --yes --generate-code=false aws:ssm/parameter:Parameter ssm_equitypricemodel_model_version "/fund/equitypricemodel/model_version" 2>/dev/null || true +pulumi import --yes --generate-code=false aws:ssm/parameter:Parameter ssm_ensemble_manager_model_version "/fund/ensemble-manager/model_version" 2>/dev/null || true echo "Importing resources complete" @@ -265,7 +270,7 @@ else # Note: Service names use 'fund' prefix matching the Pulumi project name. # These must exactly match the ECS service names created by the infrastructure code. # The AWS account provides environment context (one account = one environment). - for service in fund-datamanager fund-portfoliomanager fund-equitypricemodel fund-prefect-server fund-prefect-worker; do + for service in fund-data-manager fund-portfolio-manager fund-ensemble-manager fund-training-server fund-training-worker; do echo "Checking if $service exists and is ready" # Wait up to 60 seconds for service to be active @@ -309,7 +314,7 @@ else done echo "Stack update complete - ECS is performing rolling deployments" - echo "Monitor progress: aws ecs describe-services --cluster $cluster --services fund-portfoliomanager" + echo "Monitor progress: aws ecs describe-services --cluster $cluster --services fund-portfolio-manager" fi echo "Infrastructure launched successfully" @@ -369,7 +374,7 @@ fi cd "${MASKFILE_DIR}" case "$application_name" in - portfoliomanager) + portfolio-manager) full_url="${base_url}/portfolio" echo "Creating portfolio: $full_url" @@ -386,7 +391,7 @@ case "$application_name" in fi ;; - datamanager) + data-manager) if [ -z "${data_type:-}" ]; then echo "Missing required flag: --data-type" echo "Valid choices: equity-bars, equity-details" @@ -408,7 +413,7 @@ case "$application_name" in *) echo "Unknown application name: ${application_name}" - echo "Valid options: portfoliomanager, datamanager" + echo "Valid options: portfolio-manager, data-manager" exit 1 ;; esac @@ -761,46 +766,17 @@ mask development yaml lint echo "YAML development checks completed successfully" ``` -## models +## model > Model management commands -### prepare (application_name) - -> Prepare training data by consolidating equity bars with categories - -```bash -set -euo pipefail - -export APPLICATION_NAME="${application_name}" - -cd infrastructure - -if ! organization_name=$(pulumi org get-default 2>/dev/null) || [ -z "${organization_name}" ]; then - echo "Unable to determine Pulumi organization name - ensure you are logged in" - exit 1 -fi - -pulumi stack select ${organization_name}/fund/production - -export AWS_S3_DATA_BUCKET_NAME="$(pulumi stack output aws_s3_data_bucket_name)" -export AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME="$(pulumi stack output aws_s3_model_artifacts_bucket_name)" -export LOOKBACK_DAYS="${LOOKBACK_DAYS:-365}" - -cd ../ - -uv run python -m tools.prepare_training_data -``` - -### train (application_name) +### train > Train model via Prefect training pipeline ```bash set -euo pipefail -export APPLICATION_NAME="${application_name}" - cd infrastructure if ! organization_name=$(pulumi org get-default 2>/dev/null) || [ -z "${organization_name}" ]; then @@ -813,23 +789,21 @@ pulumi stack select ${organization_name}/fund/production export FUND_DATAMANAGER_BASE_URL="$(pulumi stack output aws_alb_url)" export AWS_S3_DATA_BUCKET_NAME="$(pulumi stack output aws_s3_data_bucket_name)" export AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME="$(pulumi stack output aws_s3_model_artifacts_bucket_name)" -export PREFECT_API_URL="$(pulumi stack output prefect_api_url)" -export LOOKBACK_DAYS="${LOOKBACK_DAYS:-365}" +export PREFECT_API_URL="$(pulumi stack output training_api_url)" +export FUND_LOOKBACK_DAYS="${FUND_LOOKBACK_DAYS:-365}" cd ../ -uv run python -m tools.run_training_job +uv run python -m tide.run ``` -### deploy (application_name) +### deploy > Register flow deployment with Prefect server ```bash set -euo pipefail -export APPLICATION_NAME="${application_name}" - cd infrastructure if ! organization_name=$(pulumi org get-default 2>/dev/null) || [ -z "${organization_name}" ]; then @@ -839,22 +813,18 @@ fi pulumi stack select ${organization_name}/fund/production -export FUND_DATAMANAGER_BASE_URL="http://datamanager.$(pulumi stack output aws_service_discovery_namespace):8080" +export FUND_DATAMANAGER_BASE_URL="http://data-manager.$(pulumi stack output aws_service_discovery_namespace):8080" export AWS_S3_DATA_BUCKET_NAME="$(pulumi stack output aws_s3_data_bucket_name)" export AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME="$(pulumi stack output aws_s3_model_artifacts_bucket_name)" -export PREFECT_API_URL="$(pulumi stack output prefect_api_url)" -export LOOKBACK_DAYS="${LOOKBACK_DAYS:-365}" +export PREFECT_API_URL="$(pulumi stack output training_api_url)" +export FUND_LOOKBACK_DAYS="${FUND_LOOKBACK_DAYS:-365}" cd ../ -uv run python -m tools.deploy_training_flow +uv run python -m tide.deploy ``` -### artifacts - -> Manage model artifacts - -#### download (application_name) +### download (application_name) > Download model artifacts @@ -866,51 +836,6 @@ export APPLICATION_NAME="${application_name}" uv run python -m tools.download_model_artifacts ``` -## prefect - -> Prefect infrastructure management - -### build-worker - -> Build and push the Prefect worker Docker image to ECR - -```bash -set -euo pipefail - -echo "Building Prefect worker image" - -aws_account_id=$(aws sts get-caller-identity --query Account --output text) -aws_region=${AWS_REGION} -if [ -z "$aws_region" ]; then - echo "AWS_REGION environment variable is not set" - exit 1 -fi - -image_reference="${aws_account_id}.dkr.ecr.${aws_region}.amazonaws.com/fund/prefect-worker" - -echo "Logging into ECR" -aws ecr get-login-password --region ${aws_region} | docker login \ - --username AWS \ - --password-stdin ${aws_account_id}.dkr.ecr.${aws_region}.amazonaws.com > /dev/null - -echo "Building image for linux/amd64" -docker build \ - --platform linux/amd64 \ - --target worker \ - --file tools/Dockerfile \ - --tag ${image_reference}:latest \ - . - -echo "Pushing image to ECR" -docker push ${image_reference}:latest - -commit_hash=$(git rev-parse --short HEAD) -docker tag "${image_reference}:latest" "${image_reference}:git-${commit_hash}" -docker push "${image_reference}:git-${commit_hash}" - -echo "Prefect worker image pushed: ${image_reference}:latest (commit: ${commit_hash})" -``` - ## mcp > MCP server management diff --git a/tools/Dockerfile b/tools/Dockerfile index f78abea10..873d2e97e 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -12,7 +12,7 @@ COPY pyproject.toml uv.lock ./ COPY tools/ tools/ -COPY applications/equitypricemodel/ applications/equitypricemodel/ +COPY applications/ensemble_manager/ applications/ensemble_manager/ COPY libraries/python/ libraries/python/ @@ -28,7 +28,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv COPY --from=builder /app /app -ENV PYTHONPATH=/app/tools/src:/app/applications/equitypricemodel/src:/app/libraries/python/src +ENV PYTHONPATH=/app/tools/src:/app/applications/ensemble_manager/src:/app/libraries/python/src ENV HOME=/home/worker RUN mkdir -p /home/worker && chown -R worker:worker /home/worker /app && \ From 9fcaddd6296da787b75897f7ce73a5a4b5295158 Mon Sep 17 00:00:00 2001 From: John Forstmeier Date: Fri, 13 Mar 2026 16:12:31 -0400 Subject: [PATCH 4/6] Refactor infrastructure definitions into separate files --- infrastructure/__main__.py | 2265 +---------------- infrastructure/compute.py | 1013 ++++++++ infrastructure/config.py | 155 ++ infrastructure/iam.py | 570 +++++ infrastructure/networking.py | 304 +++ infrastructure/notifications.py | 50 + infrastructure/secrets.py | 68 + infrastructure/storage.py | 182 ++ .../tests/test_infrastructure_config.py | 54 + .../test_infrastructure_configuration.py | 102 - .../python/tests/test_infrastructure_iam.py | 32 + .../tests/test_infrastructure_networking.py | 18 + .../tests/test_infrastructure_storage.py | 22 + 13 files changed, 2485 insertions(+), 2350 deletions(-) create mode 100644 infrastructure/compute.py create mode 100644 infrastructure/config.py create mode 100644 infrastructure/iam.py create mode 100644 infrastructure/networking.py create mode 100644 infrastructure/notifications.py create mode 100644 infrastructure/secrets.py create mode 100644 infrastructure/storage.py create mode 100644 libraries/python/tests/test_infrastructure_config.py delete mode 100644 libraries/python/tests/test_infrastructure_configuration.py create mode 100644 libraries/python/tests/test_infrastructure_iam.py create mode 100644 libraries/python/tests/test_infrastructure_networking.py create mode 100644 libraries/python/tests/test_infrastructure_storage.py diff --git a/infrastructure/__main__.py b/infrastructure/__main__.py index f06b16d5c..6a78996ca 100644 --- a/infrastructure/__main__.py +++ b/infrastructure/__main__.py @@ -1,2252 +1,21 @@ -import json -from typing import cast - -import parameters import pulumi -import pulumi_aws as aws - - -def require_secret_config_object( - config: pulumi.Config, - key: str, -) -> pulumi.Output[dict[str, str]]: - config_full_key = config.full_key(key) - if not pulumi.runtime.is_config_secret(config_full_key): - message = f"Pulumi config '{key}' must be configured as a secret object." - raise ValueError(message) - - return cast( - "pulumi.Output[dict[str, str]]", - config.require_secret_object(key), - ) - - -def serialize_secret_config_object( - secret_values: dict[str, str], - config_key: str, - required_keys: set[str], -) -> str: - missing_secret_keys = sorted(required_keys.difference(secret_values)) - if missing_secret_keys: - message = ( - f"Pulumi config '{config_key}' is missing required keys: " - f"{', '.join(missing_secret_keys)}." - ) - raise ValueError(message) - - return json.dumps(secret_values, sort_keys=True) - - -stack_name = pulumi.get_stack() -if stack_name != "production": - message = "Only the production Pulumi stack is supported." - raise ValueError(message) - -stack_config = pulumi.Config("fund") -aws_config = pulumi.Config("aws") - -region = aws_config.require("region") - -random_suffix = stack_config.require_secret("randomSuffix") - -github_actions_role_name = stack_config.require("githubActionsRoleName") -github_repository = stack_config.require("githubRepository") -github_branch = stack_config.require("githubBranch") -github_workflow_files = cast( - "list[str]", - stack_config.require_object("githubWorkflowFiles"), -) -if not github_workflow_files: - message = ( - "Pulumi config 'githubWorkflowFiles' must include at least one workflow file." - ) - raise ValueError(message) - -budget_alert_email_addresses_full_key = stack_config.full_key( - "budgetAlertEmailAddresses" -) -if not pulumi.runtime.is_config_secret(budget_alert_email_addresses_full_key): - message = ( - "Pulumi config 'budgetAlertEmailAddresses' must be configured as a secret list." - ) - raise ValueError(message) -budget_alert_email_addresses = cast( - "list[str]", - stack_config.require_object("budgetAlertEmailAddresses"), -) -if not budget_alert_email_addresses: - message = ( - "Pulumi config 'budgetAlertEmailAddresses' must include at least one email " - "address." - ) - raise ValueError(message) - -monthly_budget_limit_usd = stack_config.require_float("monthlyBudgetLimitUsd") - -prefect_allowed_cidrs = cast( - "list[str]", - stack_config.require_object("prefectAllowedCidrs"), -) -if not prefect_allowed_cidrs: - message = ( - "Pulumi config 'prefectAllowedCidrs' must include at least one CIDR block." - ) - raise ValueError(message) - -prefect_allowed_ipv4_cidrs = [c for c in prefect_allowed_cidrs if ":" not in c] -prefect_allowed_ipv6_cidrs = [c for c in prefect_allowed_cidrs if ":" in c] - -training_notification_sender_email = stack_config.require_secret( - "trainingNotificationSenderEmail" -) -training_notification_recipient_emails = stack_config.require_secret( - "trainingNotificationRecipientEmails" -) - -data_manager_secret_name = stack_config.require_secret("datamanagerSecretName") -portfolio_manager_secret_name = stack_config.require_secret( - "portfoliomanagerSecretName" -) -shared_secret_name = stack_config.require_secret("sharedSecretName") - -data_manager_secret_values = require_secret_config_object( - stack_config, - "datamanagerSecretValue", -) -portfolio_manager_secret_values = require_secret_config_object( - stack_config, - "portfoliomanagerSecretValue", -) -shared_secret_values = require_secret_config_object( - stack_config, - "sharedSecretValue", -) - -github_oidc_audience_claim = "token.actions.githubusercontent.com:aud" -github_oidc_repository_claim = "token.actions.githubusercontent.com:repository" -github_oidc_ref_claim = "token.actions.githubusercontent.com:ref" -github_oidc_sub_claim = "token.actions.githubusercontent.com:sub" -github_oidc_workflow_ref_claim = "token.actions.githubusercontent.com:job_workflow_ref" - -github_workflow_refs = [ - ( - f"{github_repository}/.github/workflows/{github_workflow_file}" - f"@refs/heads/{github_branch}" - ) - for github_workflow_file in github_workflow_files -] - -current_identity = aws.get_caller_identity() - -account_id = current_identity.account_id - -availability_zone_a = f"{region}a" -availability_zone_b = f"{region}b" - -tags = { - "project": "fund", - "stack": stack_name, - "manager": "pulumi", -} - -github_oidc_provider_arn = pulumi.Output.concat( - "arn:aws:iam::", - account_id, - ":oidc-provider/token.actions.githubusercontent.com", -) - -data_manager_secret = aws.secretsmanager.Secret( - "data_manager_secret", - name=data_manager_secret_name, - recovery_window_in_days=0, - tags=tags, -) - -portfolio_manager_secret = aws.secretsmanager.Secret( - "portfolio_manager_secret", - name=portfolio_manager_secret_name, - recovery_window_in_days=0, - tags=tags, -) - -shared_secret = aws.secretsmanager.Secret( - "shared_secret", - name=shared_secret_name, - recovery_window_in_days=0, - tags=tags, -) - -aws.secretsmanager.SecretVersion( - "data_manager_secret_version", - secret_id=data_manager_secret.id, - secret_string=data_manager_secret_values.apply( - lambda values: serialize_secret_config_object( - values, - "datamanagerSecretValue", - {"MASSIVE_API_KEY"}, - ) - ), -) - -aws.secretsmanager.SecretVersion( - "portfolio_manager_secret_version", - secret_id=portfolio_manager_secret.id, - secret_string=portfolio_manager_secret_values.apply( - lambda values: serialize_secret_config_object( - values, - "portfoliomanagerSecretValue", - {"ALPACA_API_KEY_ID", "ALPACA_API_SECRET", "ALPACA_IS_PAPER"}, - ) - ), -) - -aws.secretsmanager.SecretVersion( - "shared_secret_version", - secret_id=shared_secret.id, - secret_string=shared_secret_values.apply( - lambda values: serialize_secret_config_object( - values, - "sharedSecretValue", - {"SENTRY_DSN"}, - ) - ), -) - -infrastructure_alerts_topic = aws.sns.Topic( - "infrastructure_alerts_topic", - name="fund-infrastructure-alerts", - tags=tags, -) - -for notification_email_index, notification_email_address in enumerate( - budget_alert_email_addresses, - start=1, -): - aws.sns.TopicSubscription( - f"infrastructure_alert_email_subscription_{notification_email_index}", - topic=infrastructure_alerts_topic.arn, - protocol="email", - endpoint=notification_email_address, - ) - -aws.budgets.Budget( - "production_cost_budget", - account_id=account_id, - name="fund-monthly-cost", - budget_type="COST", - time_unit="MONTHLY", - limit_amount=f"{monthly_budget_limit_usd:.2f}", - limit_unit="USD", - notifications=[ - aws.budgets.BudgetNotificationArgs( - comparison_operator="GREATER_THAN", - notification_type="ACTUAL", - threshold=monthly_budget_limit_usd, - threshold_type="ABSOLUTE_VALUE", - subscriber_email_addresses=budget_alert_email_addresses, - ), - aws.budgets.BudgetNotificationArgs( - comparison_operator="GREATER_THAN", - notification_type="FORECASTED", - threshold=monthly_budget_limit_usd, - threshold_type="ABSOLUTE_VALUE", - subscriber_email_addresses=budget_alert_email_addresses, - ), - ], -) - -# S3 Data Bucket for storing equity bars, predictions, portfolios -# alias: migrated from aws:s3/bucket:Bucket to aws:s3/bucketV2:BucketV2 -data_bucket = aws.s3.BucketV2( - "data_bucket", - bucket=pulumi.Output.concat("fund-data-", random_suffix), - opts=pulumi.ResourceOptions( - retain_on_delete=True, - aliases=[pulumi.Alias(type_="aws:s3/bucket:Bucket")], - ), - tags=tags, -) - -aws.s3.BucketServerSideEncryptionConfiguration( - "data_bucket_encryption", - bucket=data_bucket.id, - rules=[ - aws.s3.BucketServerSideEncryptionConfigurationRuleArgs( - apply_server_side_encryption_by_default=aws.s3.BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefaultArgs( - sse_algorithm="AES256", - ), - ) - ], - opts=pulumi.ResourceOptions(retain_on_delete=True), -) - -aws.s3.BucketPublicAccessBlock( - "data_bucket_public_access_block", - bucket=data_bucket.id, - block_public_acls=True, - block_public_policy=True, - ignore_public_acls=True, - restrict_public_buckets=True, - opts=pulumi.ResourceOptions(retain_on_delete=True), -) - -aws.s3.BucketVersioning( - "data_bucket_versioning", - bucket=data_bucket.id, - versioning_configuration=aws.s3.BucketVersioningVersioningConfigurationArgs( - status="Enabled", - ), - opts=pulumi.ResourceOptions(retain_on_delete=True), -) - -# S3 Model Artifacts Bucket for storing trained model weights and checkpoints -# alias: migrated from aws:s3/bucket:Bucket to aws:s3/bucketV2:BucketV2 -model_artifacts_bucket = aws.s3.BucketV2( - "model_artifacts_bucket", - bucket=pulumi.Output.concat("fund-model-artifacts-", random_suffix), - opts=pulumi.ResourceOptions( - retain_on_delete=True, - aliases=[pulumi.Alias(type_="aws:s3/bucket:Bucket")], - ), - tags=tags, -) - -aws.s3.BucketServerSideEncryptionConfiguration( - "model_artifacts_bucket_encryption", - bucket=model_artifacts_bucket.id, - rules=[ - aws.s3.BucketServerSideEncryptionConfigurationRuleArgs( - apply_server_side_encryption_by_default=aws.s3.BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefaultArgs( - sse_algorithm="AES256", - ), - ) - ], - opts=pulumi.ResourceOptions(retain_on_delete=True), -) - -aws.s3.BucketPublicAccessBlock( - "model_artifacts_bucket_public_access_block", - bucket=model_artifacts_bucket.id, - block_public_acls=True, - block_public_policy=True, - ignore_public_acls=True, - restrict_public_buckets=True, - opts=pulumi.ResourceOptions(retain_on_delete=True), -) - -aws.s3.BucketVersioning( - "model_artifacts_bucket_versioning", - bucket=model_artifacts_bucket.id, - versioning_configuration=aws.s3.BucketVersioningVersioningConfigurationArgs( - status="Enabled", - ), - opts=pulumi.ResourceOptions(retain_on_delete=True), -) - -# ECR Repositories - these must exist before images can be pushed -# force_delete allows repositories containing images to be deleted on stack teardown. -# If image rebuild and push times become prohibitive on daily down/up cycles, switch to -# retain_on_delete=True and add pulumi import statements to the maskfile up command. -data_manager_repository = aws.ecr.Repository( - "data_manager_repository", - name="fund/data-manager-server", - image_tag_mutability="MUTABLE", - force_delete=True, - image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( - scan_on_push=True, - ), - tags=tags, -) - -portfolio_manager_repository = aws.ecr.Repository( - "portfolio_manager_repository", - name="fund/portfolio-manager-server", - image_tag_mutability="MUTABLE", - force_delete=True, - image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( - scan_on_push=True, - ), - tags=tags, -) - -ensemble_manager_repository = aws.ecr.Repository( - "ensemble_manager_repository", - name="fund/ensemble-manager-server", - image_tag_mutability="MUTABLE", - force_delete=True, - image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( - scan_on_push=True, - ), - tags=tags, -) - -tide_trainer_repository = aws.ecr.Repository( - "tide_trainer_repository", - name="fund/tide-trainer", - image_tag_mutability="MUTABLE", - force_delete=True, - image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( - scan_on_push=True, - ), - tags=tags, -) - -training_server_repository = aws.ecr.Repository( - "training_server_repository", - name="fund/training-server", - image_tag_mutability="MUTABLE", - force_delete=True, - image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( - scan_on_push=True, - ), - tags=tags, -) - -training_worker_repository = aws.ecr.Repository( - "training_worker_repository", - name="fund/training-worker", - image_tag_mutability="MUTABLE", - force_delete=True, - image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( - scan_on_push=True, - ), - tags=tags, -) - -# Generate image URIs - these will be used in task definitions -# For initial deployment, use a placeholder that will be updated when images are pushed -data_manager_image_uri = data_manager_repository.repository_url.apply( - lambda url: f"{url}:latest" -) -portfolio_manager_image_uri = portfolio_manager_repository.repository_url.apply( - lambda url: f"{url}:latest" -) -ensemble_manager_image_uri = ensemble_manager_repository.repository_url.apply( - lambda url: f"{url}:latest" -) -tide_trainer_image_uri = tide_trainer_repository.repository_url.apply( - lambda url: f"{url}:latest" -) -training_server_image_uri = training_server_repository.repository_url.apply( - lambda url: f"{url}:latest" -) -training_worker_image_uri = training_worker_repository.repository_url.apply( - lambda url: f"{url}:latest" -) - -vpc = aws.ec2.Vpc( - "vpc", - cidr_block="10.0.0.0/16", - enable_dns_hostnames=True, - enable_dns_support=True, - tags=tags, -) - -# Internet Gateway for public subnets -igw = aws.ec2.InternetGateway( - "igw", - vpc_id=vpc.id, - tags=tags, -) - -# Public subnets for ALB -public_subnet_1 = aws.ec2.Subnet( - "public_subnet_1", - vpc_id=vpc.id, - cidr_block="10.0.1.0/24", - availability_zone=availability_zone_a, - map_public_ip_on_launch=True, - tags=tags, -) - -public_subnet_2 = aws.ec2.Subnet( - "public_subnet_2", - vpc_id=vpc.id, - cidr_block="10.0.2.0/24", - availability_zone=availability_zone_b, - map_public_ip_on_launch=True, - tags=tags, -) - -# Private subnets for ECS tasks -private_subnet_1 = aws.ec2.Subnet( - "private_subnet_1", - vpc_id=vpc.id, - cidr_block="10.0.3.0/24", - availability_zone=availability_zone_a, - tags=tags, -) - -private_subnet_2 = aws.ec2.Subnet( - "private_subnet_2", - vpc_id=vpc.id, - cidr_block="10.0.4.0/24", - availability_zone=availability_zone_b, - tags=tags, -) - -public_route_table = aws.ec2.RouteTable( - "public_route_table", - vpc_id=vpc.id, - tags=tags, -) - -aws.ec2.Route( - "public_internet_route", - route_table_id=public_route_table.id, - destination_cidr_block="0.0.0.0/0", - gateway_id=igw.id, -) - -aws.ec2.RouteTableAssociation( - "public_subnet_1_rta", - subnet_id=public_subnet_1.id, - route_table_id=public_route_table.id, -) - -aws.ec2.RouteTableAssociation( - "public_subnet_2_rta", - subnet_id=public_subnet_2.id, - route_table_id=public_route_table.id, -) - -eip = aws.ec2.Eip( - "nat_elastic_ip", - domain="vpc", - tags=tags, -) - -# NAT Gateway in public subnet for private subnet outbound traffic -nat = aws.ec2.NatGateway( - "nat_gateway", - subnet_id=public_subnet_1.id, - allocation_id=eip.id, - tags=tags, -) - -aws.cloudwatch.MetricAlarm( - "nat_gateway_bytes_out_to_destination_alarm", - name="fund-nat-gateway-bytes-out-to-destination", - alarm_description=( - "Triggers when NAT gateway outbound traffic exceeds 500 MB per hour for " - "2 consecutive hours." - ), - namespace="AWS/NATGateway", - metric_name="BytesOutToDestination", - statistic="Sum", - period=3600, - evaluation_periods=2, - threshold=500_000_000, - comparison_operator="GreaterThanThreshold", - treat_missing_data="notBreaching", - dimensions={"NatGatewayId": nat.id}, - alarm_actions=[infrastructure_alerts_topic.arn], - ok_actions=[infrastructure_alerts_topic.arn], - tags=tags, -) - -private_route_table = aws.ec2.RouteTable( - "private_route_table", - vpc_id=vpc.id, - tags=tags, -) - -aws.ec2.Route( - "nat_route", - route_table_id=private_route_table.id, - destination_cidr_block="0.0.0.0/0", - nat_gateway_id=nat.id, -) - -aws.ec2.RouteTableAssociation( - "private_subnet_1_rta", - subnet_id=private_subnet_1.id, - route_table_id=private_route_table.id, -) - -aws.ec2.RouteTableAssociation( - "private_subnet_2_rta", - subnet_id=private_subnet_2.id, - route_table_id=private_route_table.id, -) - -alb_security_group = aws.ec2.SecurityGroup( - "alb_sg", - name="fund-alb", - vpc_id=vpc.id, - description="Security group for ALB", - ingress=[ - aws.ec2.SecurityGroupIngressArgs( - protocol="tcp", - from_port=80, - to_port=80, - cidr_blocks=["0.0.0.0/0"], - description="Allow HTTP", - ), - aws.ec2.SecurityGroupIngressArgs( - protocol="tcp", - from_port=443, - to_port=443, - cidr_blocks=["0.0.0.0/0"], - description="Allow HTTPS", - ), - *( - [ - aws.ec2.SecurityGroupIngressArgs( - protocol="tcp", - from_port=4200, - to_port=4200, - cidr_blocks=prefect_allowed_ipv4_cidrs, - description="Allow Prefect dashboard from team IPv4", - ), - ] - if prefect_allowed_ipv4_cidrs - else [] - ), - *( - [ - aws.ec2.SecurityGroupIngressArgs( - protocol="tcp", - from_port=4200, - to_port=4200, - ipv6_cidr_blocks=prefect_allowed_ipv6_cidrs, - description="Allow Prefect dashboard from team IPv6", - ), - ] - if prefect_allowed_ipv6_cidrs - else [] - ), - ], - egress=[ - aws.ec2.SecurityGroupEgressArgs( - protocol="-1", - from_port=0, - to_port=0, - cidr_blocks=["0.0.0.0/0"], - description="Allow all outbound", - ) - ], - tags=tags, -) - -ecs_security_group = aws.ec2.SecurityGroup( - "ecs_sg", - name="fund-ecs-tasks", - vpc_id=vpc.id, - description="Security group for ECS tasks", - tags=tags, -) - -# Allow ALB to reach ECS tasks on port 8080 -aws.ec2.SecurityGroupRule( - "ecs_from_alb", - type="ingress", - security_group_id=ecs_security_group.id, - source_security_group_id=alb_security_group.id, - protocol="tcp", - from_port=8080, - to_port=8080, - description="Allow ALB traffic", -) - -# Allow ECS tasks to communicate with each other -aws.ec2.SecurityGroupRule( - "ecs_self_ingress", - type="ingress", - security_group_id=ecs_security_group.id, - source_security_group_id=ecs_security_group.id, - protocol="tcp", - from_port=8080, - to_port=8080, - description="Allow inter-service communication", -) - -# Allow all outbound traffic from ECS tasks -aws.ec2.SecurityGroupRule( - "ecs_egress", - type="egress", - security_group_id=ecs_security_group.id, - protocol="-1", - from_port=0, - to_port=0, - cidr_blocks=["0.0.0.0/0"], - description="Allow all outbound", -) - -# VPC Endpoints Security Group -vpc_endpoints_security_group = aws.ec2.SecurityGroup( - "vpc_endpoints_sg", - name="fund-vpc-endpoints", - vpc_id=vpc.id, - description="Security group for VPC endpoints", - tags=tags, -) - -aws.ec2.SecurityGroupRule( - "vpc_endpoints_ingress", - type="ingress", - security_group_id=vpc_endpoints_security_group.id, - source_security_group_id=ecs_security_group.id, - protocol="tcp", - from_port=443, - to_port=443, - description="Allow HTTPS from ECS tasks", -) - -# S3 Gateway Endpoint -s3_gateway_endpoint = aws.ec2.VpcEndpoint( - "s3_gateway_endpoint", - vpc_id=vpc.id, - service_name=pulumi.Output.concat("com.amazonaws.", region, ".s3"), - vpc_endpoint_type="Gateway", - route_table_ids=[private_route_table.id], - tags=tags, -) - -# ECR API Interface Endpoint -ecr_api_endpoint = aws.ec2.VpcEndpoint( - "ecr_api_endpoint", - vpc_id=vpc.id, - service_name=pulumi.Output.concat("com.amazonaws.", region, ".ecr.api"), - vpc_endpoint_type="Interface", - subnet_ids=[private_subnet_1.id, private_subnet_2.id], - security_group_ids=[vpc_endpoints_security_group.id], - private_dns_enabled=True, - tags=tags, -) - -# ECR DKR Interface Endpoint -ecr_dkr_endpoint = aws.ec2.VpcEndpoint( - "ecr_dkr_endpoint", - vpc_id=vpc.id, - service_name=pulumi.Output.concat("com.amazonaws.", region, ".ecr.dkr"), - vpc_endpoint_type="Interface", - subnet_ids=[private_subnet_1.id, private_subnet_2.id], - security_group_ids=[vpc_endpoints_security_group.id], - private_dns_enabled=True, - tags=tags, -) - -cluster = aws.ecs.Cluster( - "ecs_cluster", - name="fund-application", - settings=[aws.ecs.ClusterSettingArgs(name="containerInsights", value="enabled")], - tags=tags, -) - -# Service Discovery Namespace for inter-service communication -service_discovery_namespace = aws.servicediscovery.PrivateDnsNamespace( - "service_discovery", - name="fund.local", - vpc=vpc.id, - description="Service discovery for fund services", - tags=tags, -) - -alb = aws.lb.LoadBalancer( - "alb", - name="fund-alb", - subnets=[public_subnet_1.id, public_subnet_2.id], - security_groups=[alb_security_group.id], - internal=False, - load_balancer_type="application", - tags=tags, -) - -data_manager_tg = aws.lb.TargetGroup( - "data_manager_tg", - name="fund-data-manager", - port=8080, - protocol="HTTP", - vpc_id=vpc.id, - target_type="ip", - health_check=aws.lb.TargetGroupHealthCheckArgs( - path="/health", - healthy_threshold=2, - unhealthy_threshold=3, - timeout=5, - interval=30, - ), - tags=tags, -) - -portfolio_manager_tg = aws.lb.TargetGroup( - "portfolio_manager_tg", - name="fund-portfolio-manager", - port=8080, - protocol="HTTP", - vpc_id=vpc.id, - target_type="ip", - health_check=aws.lb.TargetGroupHealthCheckArgs( - path="/health", - healthy_threshold=2, - unhealthy_threshold=3, - timeout=5, - interval=30, - ), - tags=tags, -) - -ensemble_manager_tg = aws.lb.TargetGroup( - "ensemble_manager_tg", - name="fund-ensemble-manager", - port=8080, - protocol="HTTP", - vpc_id=vpc.id, - target_type="ip", - health_check=aws.lb.TargetGroupHealthCheckArgs( - path="/health", - healthy_threshold=2, - unhealthy_threshold=3, - timeout=5, - interval=30, - ), - tags=tags, -) - -training_tg = aws.lb.TargetGroup( - "training_tg", - name="fund-training", - port=4200, - protocol="HTTP", - vpc_id=vpc.id, - target_type="ip", - health_check=aws.lb.TargetGroupHealthCheckArgs( - path="/api/health", - healthy_threshold=2, - unhealthy_threshold=3, - timeout=5, - interval=30, - ), - tags=tags, -) - -# Set acm_certificate_arn to enable HTTPS for the Prefect dashboard listener. -acm_certificate_arn = None - -# Prefect dashboard listener on port 4200 (restricted by ALB security group) -if acm_certificate_arn: - prefect_listener = aws.lb.Listener( - "prefect_listener", - load_balancer_arn=alb.arn, - port=4200, - protocol="HTTPS", - ssl_policy="ELBSecurityPolicy-TLS13-1-2-2021-06", - certificate_arn=acm_certificate_arn, - default_actions=[ - aws.lb.ListenerDefaultActionArgs( - type="forward", - target_group_arn=training_tg.arn, - ) - ], - tags=tags, - ) -else: - prefect_listener = aws.lb.Listener( - "prefect_listener", - load_balancer_arn=alb.arn, - port=4200, - protocol="HTTP", - default_actions=[ - aws.lb.ListenerDefaultActionArgs( - type="forward", - target_group_arn=training_tg.arn, - ) - ], - tags=tags, - ) - -if acm_certificate_arn: - # HTTPS Listener (port 443) - https_listener = aws.lb.Listener( - "https_listener", - load_balancer_arn=alb.arn, - port=443, - protocol="HTTPS", - ssl_policy="ELBSecurityPolicy-TLS13-1-2-2021-06", - certificate_arn=acm_certificate_arn, - default_actions=[ - aws.lb.ListenerDefaultActionArgs( - type="fixed-response", - fixed_response=aws.lb.ListenerDefaultActionFixedResponseArgs( - content_type="text/plain", - message_body="Not Found", - status_code="404", - ), - ) - ], - tags=tags, - ) - - # HTTP Listener (port 80) - Redirect to HTTPS - http_listener = aws.lb.Listener( - "http_listener", - load_balancer_arn=alb.arn, - port=80, - protocol="HTTP", - default_actions=[ - aws.lb.ListenerDefaultActionArgs( - type="redirect", - redirect=aws.lb.ListenerDefaultActionRedirectArgs( - protocol="HTTPS", - port="443", - status_code="HTTP_301", - ), - ) - ], - tags=tags, - ) - - alb_listener = https_listener - -else: - # HTTP-only Listener (port 80) - alb_listener = aws.lb.Listener( - "http_listener", - load_balancer_arn=alb.arn, - port=80, - protocol="HTTP", - default_actions=[ - aws.lb.ListenerDefaultActionArgs( - type="fixed-response", - fixed_response=aws.lb.ListenerDefaultActionFixedResponseArgs( - content_type="text/plain", - message_body="Not Found", - status_code="404", - ), - ) - ], - tags=tags, - ) - -# Listener Rules for routing attached to primary listener -aws.lb.ListenerRule( - "portfolio_manager_rule", - listener_arn=alb_listener.arn, - priority=200, # Ensures that the more specific data manager paths take precedence - actions=[ - aws.lb.ListenerRuleActionArgs( - type="forward", - target_group_arn=portfolio_manager_tg.arn, - ) - ], - conditions=[ - aws.lb.ListenerRuleConditionArgs( - path_pattern=aws.lb.ListenerRuleConditionPathPatternArgs( - values=["/portfolio*"] - ) - ) - ], - tags=tags, -) - -aws.lb.ListenerRule( - "data_manager_rule", - listener_arn=alb_listener.arn, - priority=100, - actions=[ - aws.lb.ListenerRuleActionArgs( - type="forward", - target_group_arn=data_manager_tg.arn, - ) - ], - conditions=[ - aws.lb.ListenerRuleConditionArgs( - path_pattern=aws.lb.ListenerRuleConditionPathPatternArgs( - values=[ - "/predictions*", - "/portfolios*", - "/equity-bars*", - "/equity-details*", - ] - ) - ) - ], - tags=tags, -) - -aws.lb.ListenerRule( - "ensemble_manager_rule", - listener_arn=alb_listener.arn, - priority=150, - actions=[ - aws.lb.ListenerRuleActionArgs( - type="forward", - target_group_arn=ensemble_manager_tg.arn, - ) - ], - conditions=[ - aws.lb.ListenerRuleConditionArgs( - path_pattern=aws.lb.ListenerRuleConditionPathPatternArgs( - values=["/model/*"] - ) - ) - ], - tags=tags, -) - -github_actions_oidc_provider = aws.iam.OpenIdConnectProvider( - "github_actions_oidc_provider", - url="https://token.actions.githubusercontent.com", - client_id_lists=["sts.amazonaws.com"], - tags=tags, -) - -github_actions_infrastructure_policy = aws.iam.Policy( - "github_actions_infrastructure_policy", - name="fund-github-actions-infrastructure-policy", - description=( - "Least-privilege policy for GitHub Actions infrastructure deployments." - ), - policy=pulumi.Output.all( - data_manager_secret_name, - portfolio_manager_secret_name, - shared_secret_name, - github_oidc_provider_arn, - ).apply( - lambda args: json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - # These list/describe APIs are account-scoped and require wildcard - # resources. - { - "Sid": "ReadGlobalMetadata", - "Effect": "Allow", - "Action": [ - "sts:GetCallerIdentity", - "tag:GetResources", - "tag:GetTagKeys", - "tag:GetTagValues", - "iam:Get*", - "iam:List*", - "ec2:Describe*", - "ecs:Describe*", - "ecs:List*", - "elasticloadbalancing:Describe*", - "ecr:Describe*", - "ecr:ListTagsForResource", - "s3:GetBucketLocation", - "s3:ListAllMyBuckets", - "ssm:DescribeParameters", - "secretsmanager:ListSecrets", - "logs:Describe*", - "cloudwatch:Describe*", - "cloudwatch:Get*", - "sns:Get*", - "sns:List*", - "budgets:Describe*", - "budgets:ViewBudget", - "servicediscovery:Get*", - "servicediscovery:List*", - ], - "Resource": "*", - }, - # These control-plane APIs rely on generated identifiers and do not - # support practical resource-level scoping for stack create/update/ - # delete operations. - { - "Sid": "ManageEC2ECSELBBudgetsAndServiceDiscovery", - "Effect": "Allow", - "Action": [ - "ec2:*", - "ecs:*", - "elasticloadbalancing:*", - "budgets:*", - "servicediscovery:*", - ], - "Resource": "*", - }, - # CreateRepository/GetAuthorizationToken require wildcard resources. - { - "Sid": "CreateAndAuthenticateECRRepositories", - "Effect": "Allow", - "Action": [ - "ecr:CreateRepository", - "ecr:GetAuthorizationToken", - ], - "Resource": "*", - }, - { - "Sid": "ManageECRRepositories", - "Effect": "Allow", - "Action": "ecr:*", - "Resource": ( - f"arn:aws:ecr:{region}:{account_id}:repository/fund/*" - ), - }, - # CreateBucket requires wildcard resources. - { - "Sid": "CreateBuckets", - "Effect": "Allow", - "Action": "s3:CreateBucket", - "Resource": "*", - }, - { - "Sid": "ManageBuckets", - "Effect": "Allow", - "Action": "s3:*", - "Resource": [ - "arn:aws:s3:::fund-data-*", - "arn:aws:s3:::fund-data-*/*", - "arn:aws:s3:::fund-model-artifacts-*", - "arn:aws:s3:::fund-model-artifacts-*/*", - ], - }, - # CreateSecret requires wildcard resources before an ARN exists. - { - "Sid": "CreateSecrets", - "Effect": "Allow", - "Action": "secretsmanager:CreateSecret", - "Resource": "*", - }, - { - "Sid": "ManageConfiguredSecrets", - "Effect": "Allow", - "Action": "secretsmanager:*", - "Resource": [ - f"arn:aws:secretsmanager:{region}:{account_id}:secret:{args[0]}*", - f"arn:aws:secretsmanager:{region}:{account_id}:secret:{args[1]}*", - f"arn:aws:secretsmanager:{region}:{account_id}:secret:{args[2]}*", - ], - }, - { - "Sid": "ManageParameters", - "Effect": "Allow", - "Action": "ssm:*", - "Resource": ( - f"arn:aws:ssm:{region}:{account_id}:parameter/fund/*" - ), - }, - { - "Sid": "ManageLogGroups", - "Effect": "Allow", - "Action": "logs:*", - "Resource": [ - f"arn:aws:logs:{region}:{account_id}:log-group:/ecs/fund/*", - f"arn:aws:logs:{region}:{account_id}:log-group:/ecs/fund/*:*", - ], - }, - # Alarm mutation APIs require wildcard resources. - { - "Sid": "ManageAlarms", - "Effect": "Allow", - "Action": [ - "cloudwatch:DeleteAlarms", - "cloudwatch:ListTagsForResource", - "cloudwatch:PutMetricAlarm", - "cloudwatch:TagResource", - "cloudwatch:UntagResource", - ], - "Resource": "*", - }, - # CreateTopic requires wildcard resources. - { - "Sid": "CreateInfrastructureAlertsTopic", - "Effect": "Allow", - "Action": "sns:CreateTopic", - "Resource": "*", - }, - { - "Sid": "ManageInfrastructureAlertsTopic", - "Effect": "Allow", - "Action": "sns:*", - "Resource": [ - f"arn:aws:sns:{region}:{account_id}:fund-infrastructure-alerts", - f"arn:aws:sns:{region}:{account_id}:fund-infrastructure-alerts:*", - ], - }, - { - "Sid": "ManageSESIdentities", - "Effect": "Allow", - "Action": [ - "ses:CreateEmailIdentity", - "ses:DeleteEmailIdentity", - "ses:GetEmailIdentity", - "ses:TagResource", - "ses:UntagResource", - "ses:ListTagsForResource", - ], - "Resource": [ - f"arn:aws:ses:{region}:{account_id}:identity/*", - ], - }, - { - "Sid": "CreateGithubActionsOIDCProvider", - "Effect": "Allow", - "Action": "iam:CreateOpenIDConnectProvider", - "Resource": args[3], - }, - # CreateRole uses wildcard resources by API design. - { - "Sid": "CreateRoles", - "Effect": "Allow", - "Action": "iam:CreateRole", - "Resource": "*", - "Condition": { - "StringEquals": { - "iam:RoleName": [ - "fund-ecs-execution-role", - "fund-ecs-task-role", - github_actions_role_name, - ] - } - }, - }, - # CreatePolicy uses wildcard resources by API design. - { - "Sid": "CreatePolicies", - "Effect": "Allow", - "Action": "iam:CreatePolicy", - "Resource": "*", - "Condition": { - "StringLike": { - "iam:PolicyName": "fund-*", - } - }, - }, - # CreateServiceLinkedRole uses wildcard resources by API design. - { - "Sid": "CreateServiceLinkedRolesForStack", - "Effect": "Allow", - "Action": "iam:CreateServiceLinkedRole", - "Resource": "*", - "Condition": { - "StringEquals": { - "iam:AWSServiceName": [ - "ecs.amazonaws.com", - "elasticloadbalancing.amazonaws.com", - ] - } - }, - }, - { - "Sid": "ManageRoles", - "Effect": "Allow", - "Action": [ - "iam:AttachRolePolicy", - "iam:DeleteRole", - "iam:DetachRolePolicy", - "iam:PassRole", - "iam:TagRole", - "iam:UntagRole", - "iam:UpdateAssumeRolePolicy", - ], - "Resource": [ - f"arn:aws:iam::{account_id}:role/fund-ecs-execution-role", - f"arn:aws:iam::{account_id}:role/fund-ecs-task-role", - f"arn:aws:iam::{account_id}:role/{github_actions_role_name}", - ], - "Condition": { - "ArnLikeIfExists": { - "iam:PolicyARN": [ - "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", - f"arn:aws:iam::{account_id}:policy/fund-*", - ] - }, - "StringLikeIfExists": { - "iam:PassedToService": [ - "ecs-tasks.amazonaws.com", - "ecs.amazonaws.com", - ] - }, - }, - }, - { - "Sid": "ManageInlineRolePolicies", - "Effect": "Allow", - "Action": [ - "iam:DeleteRolePolicy", - "iam:PutRolePolicy", - ], - "Resource": [ - f"arn:aws:iam::{account_id}:role/fund-ecs-execution-role", - f"arn:aws:iam::{account_id}:role/fund-ecs-task-role", - ], - "Condition": { - "StringEquals": { - "iam:PolicyName": [ - "fund-ecs-execution-role-secrets-policy", - "fund-ecs-task-role-s3-policy", - "fund-ecs-task-role-ssm-policy", - "fund-ecs-task-role-ses-policy", - ] - } - }, - }, - { - "Sid": "ManagePolicies", - "Effect": "Allow", - "Action": [ - "iam:CreatePolicyVersion", - "iam:DeletePolicy", - "iam:DeletePolicyVersion", - "iam:SetDefaultPolicyVersion", - "iam:TagPolicy", - "iam:UntagPolicy", - ], - "Resource": f"arn:aws:iam::{account_id}:policy/fund-*", - }, - { - "Sid": "ManageGithubActionsOIDCProvider", - "Effect": "Allow", - "Action": [ - "iam:AddClientIDToOpenIDConnectProvider", - "iam:DeleteOpenIDConnectProvider", - "iam:RemoveClientIDFromOpenIDConnectProvider", - "iam:TagOpenIDConnectProvider", - "iam:UntagOpenIDConnectProvider", - "iam:UpdateOpenIDConnectProviderThumbprint", - ], - "Resource": args[3], - }, - # Service-linked role teardown APIs are wildcard-resource only. - { - "Sid": "DeleteServiceLinkedRoles", - "Effect": "Allow", - "Action": [ - "iam:DeleteServiceLinkedRole", - "iam:GetServiceLinkedRoleDeletionStatus", - ], - "Resource": "*", - "Condition": { - "StringLikeIfExists": { - "iam:AWSServiceName": [ - "ecs.amazonaws.com", - "elasticloadbalancing.amazonaws.com", - ] - } - }, - }, - ], - }, - sort_keys=True, - ) - ), - opts=pulumi.ResourceOptions(retain_on_delete=True), - tags=tags, -) - -github_actions_infrastructure_role = aws.iam.Role( - "github_actions_infrastructure_role", - name=github_actions_role_name, - assume_role_policy=github_actions_oidc_provider.arn.apply( - lambda github_actions_oidc_provider_arn: json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Federated": github_actions_oidc_provider_arn, - }, - "Action": "sts:AssumeRoleWithWebIdentity", - "Condition": { - "StringEquals": { - github_oidc_audience_claim: "sts.amazonaws.com", - github_oidc_repository_claim: github_repository, - github_oidc_ref_claim: f"refs/heads/{github_branch}", - github_oidc_workflow_ref_claim: github_workflow_refs, - }, - "StringLike": { - github_oidc_sub_claim: f"repo:{github_repository}:*", - }, - }, - } - ], - }, - sort_keys=True, - ) - ), - managed_policy_arns=[github_actions_infrastructure_policy.arn], - opts=pulumi.ResourceOptions(retain_on_delete=True), - tags=tags, -) - -# IAM Role for ECS to perform infrastructure tasks -execution_role = aws.iam.Role( - "execution_role", - name="fund-ecs-execution-role", - assume_role_policy=json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": {"Service": "ecs-tasks.amazonaws.com"}, - } - ], - }, - sort_keys=True, - ), - tags=tags, -) - -aws.iam.RolePolicyAttachment( - "execution_role_policy", - role=execution_role.name, - policy_arn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", -) - -# Allow ECS tasks to read secrets from Secrets Manager -aws.iam.RolePolicy( - "execution_role_secrets_policy", - name="fund-ecs-execution-role-secrets-policy", - role=execution_role.id, - policy=pulumi.Output.all( - data_manager_secret.arn, - portfolio_manager_secret.arn, - shared_secret.arn, - ).apply( - lambda args: json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["secretsmanager:GetSecretValue"], - "Resource": [args[0], args[1], args[2]], - } - ], - }, - sort_keys=True, - ) - ), -) - - -# IAM Role for ECS tasks to access AWS resources -task_role = aws.iam.Role( - "task_role", - name="fund-ecs-task-role", - assume_role_policy=json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": {"Service": "ecs-tasks.amazonaws.com"}, - } - ], - }, - sort_keys=True, - ), - tags=tags, -) - -aws.iam.RolePolicy( - "task_role_s3_policy", - name="fund-ecs-task-role-s3-policy", - role=task_role.id, - policy=pulumi.Output.all(data_bucket.arn, model_artifacts_bucket.arn).apply( - lambda args: json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"], - "Resource": [ - args[0], - f"{args[0]}/*", - args[1], - f"{args[1]}/*", - ], - } - ], - }, - sort_keys=True, - ) - ), -) - -aws.iam.RolePolicy( - "task_role_ssm_policy", - name="fund-ecs-task-role-ssm-policy", - role=task_role.id, - policy=json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["ssm:GetParameter", "ssm:GetParameters"], - "Resource": f"arn:aws:ssm:{region}:{account_id}:parameter/fund/*", - } - ], - }, - sort_keys=True, - ), -) - -# SES Email Identity for training notifications -training_notification_email_identity = aws.ses.EmailIdentity( - "training_notification_email_identity", - email=training_notification_sender_email, -) - -training_notification_sender_email_parameter = aws.ssm.Parameter( - "training_notification_sender_email_parameter", - name="/fund/training/training_notification_sender_email", - type="SecureString", - value=training_notification_sender_email, - tags=tags, -) - -training_notification_recipients_parameter = aws.ssm.Parameter( - "training_notification_recipients_parameter", - name="/fund/training/training_notification_recipients", - type="SecureString", - value=training_notification_recipient_emails, - tags=tags, -) - -# Allow ECS tasks to send emails via SES -aws.iam.RolePolicy( - "task_role_ses_policy", - name="fund-ecs-task-role-ses-policy", - role=task_role.id, - policy=training_notification_email_identity.arn.apply( - lambda identity_arn: json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["ses:SendEmail", "ses:SendRawEmail"], - "Resource": identity_arn, - } - ], - }, - sort_keys=True, - ) - ), -) - -# Prefect Infrastructure - -# RDS Security Group - allows inbound Postgres from ECS tasks -prefect_rds_security_group = aws.ec2.SecurityGroup( - "prefect_rds_sg", - name="fund-prefect-rds", - vpc_id=vpc.id, - description="Security group for Prefect RDS database", - tags=tags, -) - -aws.ec2.SecurityGroupRule( - "prefect_rds_ingress", - type="ingress", - security_group_id=prefect_rds_security_group.id, - source_security_group_id=ecs_security_group.id, - protocol="tcp", - from_port=5432, - to_port=5432, - description="Allow Postgres from ECS tasks", -) - -aws.ec2.SecurityGroupRule( - "prefect_rds_egress", - type="egress", - security_group_id=prefect_rds_security_group.id, - protocol="-1", - from_port=0, - to_port=0, - cidr_blocks=["0.0.0.0/0"], - description="Allow all outbound", -) - -# Redis Security Group - allows inbound Redis from ECS tasks -prefect_redis_security_group = aws.ec2.SecurityGroup( - "prefect_redis_sg", - name="fund-prefect-redis", - vpc_id=vpc.id, - description="Security group for Prefect Redis cache", - tags=tags, -) - -aws.ec2.SecurityGroupRule( - "prefect_redis_ingress", - type="ingress", - security_group_id=prefect_redis_security_group.id, - source_security_group_id=ecs_security_group.id, - protocol="tcp", - from_port=6379, - to_port=6379, - description="Allow Redis from ECS tasks", -) - -aws.ec2.SecurityGroupRule( - "prefect_redis_egress", - type="egress", - security_group_id=prefect_redis_security_group.id, - protocol="-1", - from_port=0, - to_port=0, - cidr_blocks=["0.0.0.0/0"], - description="Allow all outbound", -) - -# RDS Subnet Group -prefect_rds_subnet_group = aws.rds.SubnetGroup( - "prefect_rds_subnet_group", - name="fund-prefect-rds", - subnet_ids=[private_subnet_1.id, private_subnet_2.id], - tags=tags, -) - -# RDS PostgreSQL for Prefect database -prefect_database = aws.rds.Instance( - "prefect_database", - identifier="fund-prefect", - engine="postgres", - engine_version="14", - instance_class="db.t3.micro", - allocated_storage=20, - db_name="prefect", - username="prefect", - manage_master_user_password=True, - db_subnet_group_name=prefect_rds_subnet_group.name, - vpc_security_group_ids=[prefect_rds_security_group.id], - skip_final_snapshot=False, - final_snapshot_identifier=f"fund-prefect-final-{pulumi.get_stack()}", - backup_retention_period=7, - storage_encrypted=True, - deletion_protection=True, - tags=tags, -) - -# Grant ECS execution role access to the RDS-managed master password secret -aws.iam.RolePolicy( - "execution_role_prefect_db_secret_policy", - name="fund-ecs-execution-role-prefect-db-secret", - role=execution_role.id, - policy=prefect_database.master_user_secrets[0]["secret_arn"].apply( - lambda arn: json.dumps( - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["secretsmanager:GetSecretValue"], - "Resource": arn, - } - ], - }, - sort_keys=True, - ) - ), -) - -# ElastiCache Subnet Group -prefect_elasticache_subnet_group = aws.elasticache.SubnetGroup( - "prefect_elasticache_subnet_group", - name="fund-prefect-redis", - subnet_ids=[private_subnet_1.id, private_subnet_2.id], - tags=tags, -) - -# ElastiCache Redis for Prefect messaging -prefect_redis = aws.elasticache.Cluster( - "prefect_redis", - cluster_id="fund-prefect-redis", - engine="redis", - engine_version="7.0", - node_type="cache.t3.micro", - num_cache_nodes=1, - subnet_group_name=prefect_elasticache_subnet_group.name, - security_group_ids=[prefect_redis_security_group.id], - tags=tags, -) - -# Allow ECS tasks to communicate with Prefect server on port 4200 -aws.ec2.SecurityGroupRule( - "ecs_prefect_ingress", - type="ingress", - security_group_id=ecs_security_group.id, - source_security_group_id=ecs_security_group.id, - protocol="tcp", - from_port=4200, - to_port=4200, - description="Allow Prefect server communication", -) - -# Allow ALB to reach Prefect server on port 4200 -aws.ec2.SecurityGroupRule( - "ecs_prefect_alb_ingress", - type="ingress", - security_group_id=ecs_security_group.id, - source_security_group_id=alb_security_group.id, - protocol="tcp", - from_port=4200, - to_port=4200, - description="Allow ALB traffic to Prefect server", -) - -# Prefect Server Log Group -training_server_log_group = aws.cloudwatch.LogGroup( - "training_server_logs", - name="/ecs/fund/training-server", - retention_in_days=7, - tags=tags, -) - -# Prefect Worker Log Group -training_worker_log_group = aws.cloudwatch.LogGroup( - "training_worker_logs", - name="/ecs/fund/training-worker", - retention_in_days=7, - tags=tags, -) - -# Prefect Server Task Definition -training_server_task_definition = aws.ecs.TaskDefinition( - "training_server_task", - family="training-server", - cpu="512", - memory="1024", - network_mode="awsvpc", - requires_compatibilities=["FARGATE"], - execution_role_arn=execution_role.arn, - task_role_arn=task_role.arn, - container_definitions=pulumi.Output.all( - training_server_log_group.name, - prefect_database.endpoint, - prefect_database.master_user_secrets[0]["secret_arn"], - training_server_image_uri, - alb.dns_name, - ).apply( - lambda args: json.dumps( - [ - { - "name": "training-server", - "image": args[3], - # Inline bash/python constructs the database URL at runtime - # because the password comes from Secrets Manager and must be - # URL-encoded before embedding in the connection string. - # Extracting this to a separate script would require building - # and deploying another Docker image. - "command": [ - "bash", - "-c", - ( - "export PREFECT_API_DATABASE_CONNECTION_URL=" - '$(python3 -c "' - "import os, urllib.parse;" - "p=urllib.parse.quote(os.environ['PREFECT_DB_PASSWORD'],safe='');" - f"print(f'postgresql+asyncpg://prefect:{{p}}@{args[1]}/prefect')" - '")' - " && prefect server start --host 0.0.0.0" - ), - ], - "portMappings": [{"containerPort": 4200, "protocol": "tcp"}], - "environment": [ - { - "name": "PREFECT_UI_API_URL", - "value": ( - f"{'https' if acm_certificate_arn else 'http'}://" - f"{args[4]}:4200/api" - ), - }, - ], - "secrets": [ - { - "name": "PREFECT_DB_PASSWORD", - "valueFrom": f"{args[2]}:password::", - }, - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": args[0], - "awslogs-region": region, - "awslogs-stream-prefix": "training-server", - }, - }, - "essential": True, - } - ], - sort_keys=True, - ) - ), - tags=tags, -) - -# Prefect Server Service Discovery -training_server_sd_service = aws.servicediscovery.Service( - "training_server_sd", - name="training-server", - dns_config=aws.servicediscovery.ServiceDnsConfigArgs( - namespace_id=service_discovery_namespace.id, - dns_records=[ - aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") - ], - ), - tags=tags, -) - -# Prefect Server ECS Service -training_server_service = aws.ecs.Service( - "training_server_service", - name="fund-training-server", - cluster=cluster.arn, - task_definition=training_server_task_definition.arn, - desired_count=1, - launch_type="FARGATE", - network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( - subnets=[private_subnet_1.id, private_subnet_2.id], - security_groups=[ecs_security_group.id], - assign_public_ip=False, - ), - load_balancers=[ - aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=training_tg.arn, - container_name="training-server", - container_port=4200, - ) - ], - service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=training_server_sd_service.arn - ), - opts=pulumi.ResourceOptions( - depends_on=[prefect_database, prefect_redis, prefect_listener], - ), - tags=tags, -) - -# Prefect Worker Task Definition -training_worker_task_definition = aws.ecs.TaskDefinition( - "training_worker_task", - family="training-worker", - cpu="4096", - memory="8192", - network_mode="awsvpc", - requires_compatibilities=["FARGATE"], - execution_role_arn=execution_role.arn, - task_role_arn=task_role.arn, - container_definitions=pulumi.Output.all( - training_worker_log_group.name, - service_discovery_namespace.name, - data_bucket.bucket, - model_artifacts_bucket.bucket, - training_worker_image_uri, - training_notification_sender_email_parameter.arn, - training_notification_recipients_parameter.arn, - ).apply( - lambda args: json.dumps( - [ - { - "name": "training-worker", - "image": args[4], - "environment": [ - { - "name": "PREFECT_API_URL", - "value": f"http://training-server.{args[1]}:4200/api", - }, - { - "name": "AWS_S3_DATA_BUCKET_NAME", - "value": args[2], - }, - { - "name": "AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME", - "value": args[3], - }, - { - "name": "FUND_DATAMANAGER_BASE_URL", - "value": f"http://data-manager.{args[1]}:8080", - }, - { - "name": "FUND_LOOKBACK_DAYS", - "value": "365", - }, - ], - "secrets": [ - { - "name": "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL", - "valueFrom": args[5], - }, - { - "name": "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS", - "valueFrom": args[6], - }, - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": args[0], - "awslogs-region": region, - "awslogs-stream-prefix": "training-worker", - }, - }, - "essential": True, - } - ], - sort_keys=True, - ) - ), - tags=tags, -) - -# Prefect Worker ECS Service -training_worker_service = aws.ecs.Service( - "training_worker_service", - name="fund-training-worker", - cluster=cluster.arn, - task_definition=training_worker_task_definition.arn, - desired_count=1, - launch_type="FARGATE", - network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( - subnets=[private_subnet_1.id, private_subnet_2.id], - security_groups=[ecs_security_group.id], - assign_public_ip=False, - ), - opts=pulumi.ResourceOptions( - depends_on=[training_server_service], - ), - tags=tags, -) - -data_manager_log_group = aws.cloudwatch.LogGroup( - "data_manager_logs", - name="/ecs/fund/data-manager", - retention_in_days=7, - tags=tags, -) - -portfolio_manager_log_group = aws.cloudwatch.LogGroup( - "portfolio_manager_logs", - name="/ecs/fund/portfolio-manager", - retention_in_days=7, - tags=tags, -) - -ensemble_manager_log_group = aws.cloudwatch.LogGroup( - "ensemble_manager_logs", - name="/ecs/fund/ensemble-manager", - retention_in_days=7, - tags=tags, -) - -data_manager_task_definition = aws.ecs.TaskDefinition( - "data_manager_task", - family="data-manager", - cpu="256", - memory="512", - network_mode="awsvpc", - requires_compatibilities=["FARGATE"], - execution_role_arn=execution_role.arn, - task_role_arn=task_role.arn, - container_definitions=pulumi.Output.all( - data_manager_log_group.name, - data_manager_image_uri, - data_manager_secret.arn, - shared_secret.arn, - data_bucket.bucket, - ).apply( - lambda args: json.dumps( - [ - { - "name": "data-manager", - "image": args[1], - "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], - "environment": [ - { - "name": "MASSIVE_BASE_URL", - "value": "https://api.massive.com", - }, - { - "name": "AWS_S3_DATA_BUCKET_NAME", - "value": args[4], - }, - { - "name": "FUND_ENVIRONMENT", - "value": "production", - }, - { - "name": "RUST_LOG", - "value": "data_manager=info,tower_http=info", - }, - ], - "secrets": [ - { - "name": "MASSIVE_API_KEY", - "valueFrom": f"{args[2]}:MASSIVE_API_KEY::", - }, - { - "name": "SENTRY_DSN", - "valueFrom": f"{args[3]}:SENTRY_DSN::", - }, - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": args[0], - "awslogs-region": region, - "awslogs-stream-prefix": "data-manager", - }, - }, - "essential": True, - } - ], - sort_keys=True, - ) - ), - tags=tags, -) - -portfolio_manager_task_definition = aws.ecs.TaskDefinition( - "portfolio_manager_task", - family="portfolio-manager", - cpu="256", - memory="512", - network_mode="awsvpc", - requires_compatibilities=["FARGATE"], - execution_role_arn=execution_role.arn, - task_role_arn=task_role.arn, - container_definitions=pulumi.Output.all( - portfolio_manager_log_group.name, - service_discovery_namespace.name, - portfolio_manager_image_uri, - portfolio_manager_secret.arn, - shared_secret.arn, - parameters.uncertainty_threshold.value, - ).apply( - lambda args: json.dumps( - [ - { - "name": "portfolio-manager", - "image": args[2], - "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], - "environment": [ - { - "name": "FUND_DATAMANAGER_BASE_URL", - "value": f"http://data-manager.{args[1]}:8080", - }, - { - "name": "FUND_ENSEMBLE_MANAGER_BASE_URL", - "value": f"http://ensemble-manager.{args[1]}:8080", - }, - { - "name": "FUND_ENVIRONMENT", - "value": "production", - }, - { - "name": "FUND_UNCERTAINTY_THRESHOLD", - "value": args[5], - }, - ], - "secrets": [ - { - "name": "ALPACA_API_KEY_ID", - "valueFrom": f"{args[3]}:ALPACA_API_KEY_ID::", - }, - { - "name": "ALPACA_API_SECRET", - "valueFrom": f"{args[3]}:ALPACA_API_SECRET::", - }, - { - "name": "ALPACA_IS_PAPER", - "valueFrom": f"{args[3]}:ALPACA_IS_PAPER::", - }, - { - "name": "SENTRY_DSN", - "valueFrom": f"{args[4]}:SENTRY_DSN::", - }, - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": args[0], - "awslogs-region": region, - "awslogs-stream-prefix": "portfolio-manager", - }, - }, - "essential": True, - } - ], - sort_keys=True, - ) - ), - tags=tags, -) - -ensemble_manager_task_definition = aws.ecs.TaskDefinition( - "ensemble_manager_task", - family="ensemble-manager", - cpu="256", - memory="512", - network_mode="awsvpc", - requires_compatibilities=["FARGATE"], - execution_role_arn=execution_role.arn, - task_role_arn=task_role.arn, - container_definitions=pulumi.Output.all( - ensemble_manager_log_group.name, - service_discovery_namespace.name, - ensemble_manager_image_uri, - model_artifacts_bucket.bucket, - shared_secret.arn, - ).apply( - lambda args: json.dumps( - [ - { - "name": "ensemble-manager", - "image": args[2], - "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], - "environment": [ - { - "name": "FUND_DATAMANAGER_BASE_URL", - "value": f"http://data-manager.{args[1]}:8080", - }, - { - "name": "AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME", - "value": args[3], - }, - { - "name": "FUND_ENVIRONMENT", - "value": "production", - }, - { - "name": "DISABLE_DISK_CACHE", - "value": "1", - }, - ], - "secrets": [ - { - "name": "SENTRY_DSN", - "valueFrom": f"{args[4]}:SENTRY_DSN::", - }, - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": args[0], - "awslogs-region": region, - "awslogs-stream-prefix": "ensemble-manager", - }, - }, - "essential": True, - } - ], - sort_keys=True, - ) - ), - tags=tags, -) - -data_manager_sd_service = aws.servicediscovery.Service( - "data_manager_sd", - name="data-manager", - dns_config=aws.servicediscovery.ServiceDnsConfigArgs( - namespace_id=service_discovery_namespace.id, - dns_records=[ - aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") - ], - ), - tags=tags, -) - -portfolio_manager_sd_service = aws.servicediscovery.Service( - "portfolio_manager_sd", - name="portfolio-manager", - dns_config=aws.servicediscovery.ServiceDnsConfigArgs( - namespace_id=service_discovery_namespace.id, - dns_records=[ - aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") - ], - ), - tags=tags, -) - -ensemble_manager_sd_service = aws.servicediscovery.Service( - "ensemble_manager_sd", - name="ensemble-manager", - dns_config=aws.servicediscovery.ServiceDnsConfigArgs( - namespace_id=service_discovery_namespace.id, - dns_records=[ - aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") - ], - ), - tags=tags, -) - -data_manager_service = aws.ecs.Service( - "data_manager_service", - name="fund-data-manager", - cluster=cluster.arn, - task_definition=data_manager_task_definition.arn, - desired_count=1, - launch_type="FARGATE", - network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( - subnets=[private_subnet_1.id, private_subnet_2.id], - security_groups=[ecs_security_group.id], - assign_public_ip=False, - ), - load_balancers=[ - aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=data_manager_tg.arn, - container_name="data-manager", - container_port=8080, - ) - ], - service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=data_manager_sd_service.arn - ), - opts=pulumi.ResourceOptions(depends_on=[alb_listener]), - tags=tags, -) - -portfolio_manager_service = aws.ecs.Service( - "portfolio_manager_service", - name="fund-portfolio-manager", - cluster=cluster.arn, - task_definition=portfolio_manager_task_definition.arn, - desired_count=1, - launch_type="FARGATE", - network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( - subnets=[private_subnet_1.id, private_subnet_2.id], - security_groups=[ecs_security_group.id], - assign_public_ip=False, - ), - load_balancers=[ - aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=portfolio_manager_tg.arn, - container_name="portfolio-manager", - container_port=8080, - ) - ], - service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=portfolio_manager_sd_service.arn - ), - opts=pulumi.ResourceOptions(depends_on=[alb_listener, data_manager_service]), - tags=tags, -) - -ensemble_manager_service = aws.ecs.Service( - "ensemble_manager_service", - name="fund-ensemble-manager", - cluster=cluster.arn, - task_definition=ensemble_manager_task_definition.arn, - desired_count=1, - launch_type="FARGATE", - network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( - subnets=[private_subnet_1.id, private_subnet_2.id], - security_groups=[ecs_security_group.id], - assign_public_ip=False, - ), - load_balancers=[ - aws.ecs.ServiceLoadBalancerArgs( - target_group_arn=ensemble_manager_tg.arn, - container_name="ensemble-manager", - container_port=8080, - ) - ], - service_registries=aws.ecs.ServiceServiceRegistriesArgs( - registry_arn=ensemble_manager_sd_service.arn - ), - opts=pulumi.ResourceOptions(depends_on=[alb_listener, data_manager_service]), - tags=tags, +from compute import acm_certificate_arn, alb, cluster, service_discovery_namespace +from config import account_id +from iam import github_actions_infrastructure_role, github_actions_oidc_provider +from networking import vpc +from storage import ( + data_bucket, + data_manager_image_uri, + data_manager_repository, + ensemble_manager_image_uri, + ensemble_manager_repository, + model_artifacts_bucket, + portfolio_manager_image_uri, + portfolio_manager_repository, + tide_trainer_image_uri, + tide_trainer_repository, + training_worker_image_uri, + training_worker_repository, ) protocol = "https://" if acm_certificate_arn else "http://" diff --git a/infrastructure/compute.py b/infrastructure/compute.py new file mode 100644 index 000000000..7850006b4 --- /dev/null +++ b/infrastructure/compute.py @@ -0,0 +1,1013 @@ +import json +from secrets import data_manager_secret, portfolio_manager_secret, shared_secret + +import parameters +import pulumi +import pulumi_aws as aws +from config import region, tags +from iam import ( + execution_role, + task_role, + training_notification_recipients_parameter, + training_notification_sender_email_parameter, +) +from networking import ( + alb_security_group, + ecs_security_group, + private_subnet_1, + private_subnet_2, + public_subnet_1, + public_subnet_2, + vpc, +) +from storage import ( + data_bucket, + data_manager_image_uri, + ensemble_manager_image_uri, + model_artifacts_bucket, + portfolio_manager_image_uri, + training_server_image_uri, + training_worker_image_uri, +) + +cluster = aws.ecs.Cluster( + "ecs_cluster", + name="fund-application", + settings=[aws.ecs.ClusterSettingArgs(name="containerInsights", value="enabled")], + tags=tags, +) + +# Service Discovery Namespace for inter-service communication +service_discovery_namespace = aws.servicediscovery.PrivateDnsNamespace( + "service_discovery", + name="fund.local", + vpc=vpc.id, + description="Service discovery for fund services", + tags=tags, +) + +alb = aws.lb.LoadBalancer( + "alb", + name="fund-alb", + subnets=[public_subnet_1.id, public_subnet_2.id], + security_groups=[alb_security_group.id], + internal=False, + load_balancer_type="application", + tags=tags, +) + +data_manager_tg = aws.lb.TargetGroup( + "data_manager_tg", + name="fund-data-manager", + port=8080, + protocol="HTTP", + vpc_id=vpc.id, + target_type="ip", + health_check=aws.lb.TargetGroupHealthCheckArgs( + path="/health", + healthy_threshold=2, + unhealthy_threshold=3, + timeout=5, + interval=30, + ), + tags=tags, +) + +portfolio_manager_tg = aws.lb.TargetGroup( + "portfolio_manager_tg", + name="fund-portfolio-manager", + port=8080, + protocol="HTTP", + vpc_id=vpc.id, + target_type="ip", + health_check=aws.lb.TargetGroupHealthCheckArgs( + path="/health", + healthy_threshold=2, + unhealthy_threshold=3, + timeout=5, + interval=30, + ), + tags=tags, +) + +ensemble_manager_tg = aws.lb.TargetGroup( + "ensemble_manager_tg", + name="fund-ensemble-manager", + port=8080, + protocol="HTTP", + vpc_id=vpc.id, + target_type="ip", + health_check=aws.lb.TargetGroupHealthCheckArgs( + path="/health", + healthy_threshold=2, + unhealthy_threshold=3, + timeout=5, + interval=30, + ), + tags=tags, +) + +training_tg = aws.lb.TargetGroup( + "training_tg", + name="fund-training", + port=4200, + protocol="HTTP", + vpc_id=vpc.id, + target_type="ip", + health_check=aws.lb.TargetGroupHealthCheckArgs( + path="/api/health", + healthy_threshold=2, + unhealthy_threshold=3, + timeout=5, + interval=30, + ), + tags=tags, +) + +# Set acm_certificate_arn to enable HTTPS for the Prefect dashboard listener. +acm_certificate_arn = None + +# Prefect dashboard listener on port 4200 (restricted by ALB security group) +if acm_certificate_arn: + prefect_listener = aws.lb.Listener( + "prefect_listener", + load_balancer_arn=alb.arn, + port=4200, + protocol="HTTPS", + ssl_policy="ELBSecurityPolicy-TLS13-1-2-2021-06", + certificate_arn=acm_certificate_arn, + default_actions=[ + aws.lb.ListenerDefaultActionArgs( + type="forward", + target_group_arn=training_tg.arn, + ) + ], + tags=tags, + ) +else: + prefect_listener = aws.lb.Listener( + "prefect_listener", + load_balancer_arn=alb.arn, + port=4200, + protocol="HTTP", + default_actions=[ + aws.lb.ListenerDefaultActionArgs( + type="forward", + target_group_arn=training_tg.arn, + ) + ], + tags=tags, + ) + +if acm_certificate_arn: + # HTTPS Listener (port 443) + https_listener = aws.lb.Listener( + "https_listener", + load_balancer_arn=alb.arn, + port=443, + protocol="HTTPS", + ssl_policy="ELBSecurityPolicy-TLS13-1-2-2021-06", + certificate_arn=acm_certificate_arn, + default_actions=[ + aws.lb.ListenerDefaultActionArgs( + type="fixed-response", + fixed_response=aws.lb.ListenerDefaultActionFixedResponseArgs( + content_type="text/plain", + message_body="Not Found", + status_code="404", + ), + ) + ], + tags=tags, + ) + + # HTTP Listener (port 80) - Redirect to HTTPS + http_listener = aws.lb.Listener( + "http_listener", + load_balancer_arn=alb.arn, + port=80, + protocol="HTTP", + default_actions=[ + aws.lb.ListenerDefaultActionArgs( + type="redirect", + redirect=aws.lb.ListenerDefaultActionRedirectArgs( + protocol="HTTPS", + port="443", + status_code="HTTP_301", + ), + ) + ], + tags=tags, + ) + + alb_listener = https_listener + +else: + # HTTP-only Listener (port 80) + alb_listener = aws.lb.Listener( + "http_listener", + load_balancer_arn=alb.arn, + port=80, + protocol="HTTP", + default_actions=[ + aws.lb.ListenerDefaultActionArgs( + type="fixed-response", + fixed_response=aws.lb.ListenerDefaultActionFixedResponseArgs( + content_type="text/plain", + message_body="Not Found", + status_code="404", + ), + ) + ], + tags=tags, + ) + +# Listener Rules for routing attached to primary listener +aws.lb.ListenerRule( + "portfolio_manager_rule", + listener_arn=alb_listener.arn, + priority=200, # Ensures that the more specific data manager paths take precedence + actions=[ + aws.lb.ListenerRuleActionArgs( + type="forward", + target_group_arn=portfolio_manager_tg.arn, + ) + ], + conditions=[ + aws.lb.ListenerRuleConditionArgs( + path_pattern=aws.lb.ListenerRuleConditionPathPatternArgs( + values=["/portfolio*"] + ) + ) + ], + tags=tags, +) + +aws.lb.ListenerRule( + "data_manager_rule", + listener_arn=alb_listener.arn, + priority=100, + actions=[ + aws.lb.ListenerRuleActionArgs( + type="forward", + target_group_arn=data_manager_tg.arn, + ) + ], + conditions=[ + aws.lb.ListenerRuleConditionArgs( + path_pattern=aws.lb.ListenerRuleConditionPathPatternArgs( + values=[ + "/predictions*", + "/portfolios*", + "/equity-bars*", + "/equity-details*", + ] + ) + ) + ], + tags=tags, +) + +aws.lb.ListenerRule( + "ensemble_manager_rule", + listener_arn=alb_listener.arn, + priority=150, + actions=[ + aws.lb.ListenerRuleActionArgs( + type="forward", + target_group_arn=ensemble_manager_tg.arn, + ) + ], + conditions=[ + aws.lb.ListenerRuleConditionArgs( + path_pattern=aws.lb.ListenerRuleConditionPathPatternArgs( + values=["/model/*"] + ) + ) + ], + tags=tags, +) + +# Prefect Infrastructure + +# RDS Security Group - allows inbound Postgres from ECS tasks +prefect_rds_security_group = aws.ec2.SecurityGroup( + "prefect_rds_sg", + name="fund-prefect-rds", + vpc_id=vpc.id, + description="Security group for Prefect RDS database", + tags=tags, +) + +aws.ec2.SecurityGroupRule( + "prefect_rds_ingress", + type="ingress", + security_group_id=prefect_rds_security_group.id, + source_security_group_id=ecs_security_group.id, + protocol="tcp", + from_port=5432, + to_port=5432, + description="Allow Postgres from ECS tasks", +) + +aws.ec2.SecurityGroupRule( + "prefect_rds_egress", + type="egress", + security_group_id=prefect_rds_security_group.id, + protocol="-1", + from_port=0, + to_port=0, + cidr_blocks=["0.0.0.0/0"], + description="Allow all outbound", +) + +# Redis Security Group - allows inbound Redis from ECS tasks +prefect_redis_security_group = aws.ec2.SecurityGroup( + "prefect_redis_sg", + name="fund-prefect-redis", + vpc_id=vpc.id, + description="Security group for Prefect Redis cache", + tags=tags, +) + +aws.ec2.SecurityGroupRule( + "prefect_redis_ingress", + type="ingress", + security_group_id=prefect_redis_security_group.id, + source_security_group_id=ecs_security_group.id, + protocol="tcp", + from_port=6379, + to_port=6379, + description="Allow Redis from ECS tasks", +) + +aws.ec2.SecurityGroupRule( + "prefect_redis_egress", + type="egress", + security_group_id=prefect_redis_security_group.id, + protocol="-1", + from_port=0, + to_port=0, + cidr_blocks=["0.0.0.0/0"], + description="Allow all outbound", +) + +# RDS Subnet Group +prefect_rds_subnet_group = aws.rds.SubnetGroup( + "prefect_rds_subnet_group", + name="fund-prefect-rds", + subnet_ids=[private_subnet_1.id, private_subnet_2.id], + tags=tags, +) + +# RDS PostgreSQL for Prefect database +prefect_database = aws.rds.Instance( + "prefect_database", + identifier="fund-prefect", + engine="postgres", + engine_version="14", + instance_class="db.t3.micro", + allocated_storage=20, + db_name="prefect", + username="prefect", + manage_master_user_password=True, + db_subnet_group_name=prefect_rds_subnet_group.name, + vpc_security_group_ids=[prefect_rds_security_group.id], + skip_final_snapshot=False, + final_snapshot_identifier=f"fund-prefect-final-{pulumi.get_stack()}", + backup_retention_period=7, + storage_encrypted=True, + deletion_protection=True, + tags=tags, +) + +# Grant ECS execution role access to the RDS-managed master password secret +aws.iam.RolePolicy( + "execution_role_prefect_db_secret_policy", + name="fund-ecs-execution-role-prefect-db-secret", + role=execution_role.id, + policy=prefect_database.master_user_secrets[0]["secret_arn"].apply( + lambda arn: json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["secretsmanager:GetSecretValue"], + "Resource": arn, + } + ], + }, + sort_keys=True, + ) + ), +) + +# ElastiCache Subnet Group +prefect_elasticache_subnet_group = aws.elasticache.SubnetGroup( + "prefect_elasticache_subnet_group", + name="fund-prefect-redis", + subnet_ids=[private_subnet_1.id, private_subnet_2.id], + tags=tags, +) + +# ElastiCache Redis for Prefect messaging +prefect_redis = aws.elasticache.Cluster( + "prefect_redis", + cluster_id="fund-prefect-redis", + engine="redis", + engine_version="7.0", + node_type="cache.t3.micro", + num_cache_nodes=1, + subnet_group_name=prefect_elasticache_subnet_group.name, + security_group_ids=[prefect_redis_security_group.id], + tags=tags, +) + +# Allow ECS tasks to communicate with Prefect server on port 4200 +aws.ec2.SecurityGroupRule( + "ecs_prefect_ingress", + type="ingress", + security_group_id=ecs_security_group.id, + source_security_group_id=ecs_security_group.id, + protocol="tcp", + from_port=4200, + to_port=4200, + description="Allow Prefect server communication", +) + +# Allow ALB to reach Prefect server on port 4200 +aws.ec2.SecurityGroupRule( + "ecs_prefect_alb_ingress", + type="ingress", + security_group_id=ecs_security_group.id, + source_security_group_id=alb_security_group.id, + protocol="tcp", + from_port=4200, + to_port=4200, + description="Allow ALB traffic to Prefect server", +) + +# Prefect Server Log Group +training_server_log_group = aws.cloudwatch.LogGroup( + "training_server_logs", + name="/ecs/fund/training-server", + retention_in_days=7, + tags=tags, +) + +# Prefect Worker Log Group +training_worker_log_group = aws.cloudwatch.LogGroup( + "training_worker_logs", + name="/ecs/fund/training-worker", + retention_in_days=7, + tags=tags, +) + +# Prefect Server Task Definition +training_server_task_definition = aws.ecs.TaskDefinition( + "training_server_task", + family="training-server", + cpu="512", + memory="1024", + network_mode="awsvpc", + requires_compatibilities=["FARGATE"], + execution_role_arn=execution_role.arn, + task_role_arn=task_role.arn, + container_definitions=pulumi.Output.all( + training_server_log_group.name, + prefect_database.endpoint, + prefect_database.master_user_secrets[0]["secret_arn"], + training_server_image_uri, + alb.dns_name, + ).apply( + lambda args: json.dumps( + [ + { + "name": "training-server", + "image": args[3], + # Inline bash/python constructs the database URL at runtime + # because the password comes from Secrets Manager and must be + # URL-encoded before embedding in the connection string. + # Extracting this to a separate script would require building + # and deploying another Docker image. + "command": [ + "bash", + "-c", + ( + "export PREFECT_API_DATABASE_CONNECTION_URL=" + '$(python3 -c "' + "import os, urllib.parse;" + "p=urllib.parse.quote(os.environ['PREFECT_DB_PASSWORD'],safe='');" + f"print(f'postgresql+asyncpg://prefect:{{p}}@{args[1]}/prefect')" + '")' + " && prefect server start --host 0.0.0.0" + ), + ], + "portMappings": [{"containerPort": 4200, "protocol": "tcp"}], + "environment": [ + { + "name": "PREFECT_UI_API_URL", + "value": ( + f"{'https' if acm_certificate_arn else 'http'}://" + f"{args[4]}:4200/api" + ), + }, + ], + "secrets": [ + { + "name": "PREFECT_DB_PASSWORD", + "valueFrom": f"{args[2]}:password::", + }, + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": args[0], + "awslogs-region": region, + "awslogs-stream-prefix": "training-server", + }, + }, + "essential": True, + } + ], + sort_keys=True, + ) + ), + tags=tags, +) + +# Prefect Server Service Discovery +training_server_sd_service = aws.servicediscovery.Service( + "training_server_sd", + name="training-server", + dns_config=aws.servicediscovery.ServiceDnsConfigArgs( + namespace_id=service_discovery_namespace.id, + dns_records=[ + aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") + ], + ), + tags=tags, +) + +# Prefect Server ECS Service +training_server_service = aws.ecs.Service( + "training_server_service", + name="fund-training-server", + cluster=cluster.arn, + task_definition=training_server_task_definition.arn, + desired_count=1, + launch_type="FARGATE", + network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( + subnets=[private_subnet_1.id, private_subnet_2.id], + security_groups=[ecs_security_group.id], + assign_public_ip=False, + ), + load_balancers=[ + aws.ecs.ServiceLoadBalancerArgs( + target_group_arn=training_tg.arn, + container_name="training-server", + container_port=4200, + ) + ], + service_registries=aws.ecs.ServiceServiceRegistriesArgs( + registry_arn=training_server_sd_service.arn + ), + opts=pulumi.ResourceOptions( + depends_on=[prefect_database, prefect_redis, prefect_listener], + ), + tags=tags, +) + +# Prefect Worker Task Definition +training_worker_task_definition = aws.ecs.TaskDefinition( + "training_worker_task", + family="training-worker", + cpu="4096", + memory="8192", + network_mode="awsvpc", + requires_compatibilities=["FARGATE"], + execution_role_arn=execution_role.arn, + task_role_arn=task_role.arn, + container_definitions=pulumi.Output.all( + training_worker_log_group.name, + service_discovery_namespace.name, + data_bucket.bucket, + model_artifacts_bucket.bucket, + training_worker_image_uri, + training_notification_sender_email_parameter.arn, + training_notification_recipients_parameter.arn, + ).apply( + lambda args: json.dumps( + [ + { + "name": "training-worker", + "image": args[4], + "environment": [ + { + "name": "PREFECT_API_URL", + "value": f"http://training-server.{args[1]}:4200/api", + }, + { + "name": "AWS_S3_DATA_BUCKET_NAME", + "value": args[2], + }, + { + "name": "AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME", + "value": args[3], + }, + { + "name": "FUND_DATAMANAGER_BASE_URL", + "value": f"http://data-manager.{args[1]}:8080", + }, + { + "name": "FUND_LOOKBACK_DAYS", + "value": "365", + }, + ], + "secrets": [ + { + "name": "FUND_TRAINING_NOTIFICATION_SENDER_EMAIL", + "valueFrom": args[5], + }, + { + "name": "FUND_TRAINING_NOTIFICATION_RECIPIENT_EMAILS", + "valueFrom": args[6], + }, + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": args[0], + "awslogs-region": region, + "awslogs-stream-prefix": "training-worker", + }, + }, + "essential": True, + } + ], + sort_keys=True, + ) + ), + tags=tags, +) + +# Prefect Worker ECS Service +training_worker_service = aws.ecs.Service( + "training_worker_service", + name="fund-training-worker", + cluster=cluster.arn, + task_definition=training_worker_task_definition.arn, + desired_count=1, + launch_type="FARGATE", + network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( + subnets=[private_subnet_1.id, private_subnet_2.id], + security_groups=[ecs_security_group.id], + assign_public_ip=False, + ), + opts=pulumi.ResourceOptions( + depends_on=[training_server_service], + ), + tags=tags, +) + +data_manager_log_group = aws.cloudwatch.LogGroup( + "data_manager_logs", + name="/ecs/fund/data-manager", + retention_in_days=7, + tags=tags, +) + +portfolio_manager_log_group = aws.cloudwatch.LogGroup( + "portfolio_manager_logs", + name="/ecs/fund/portfolio-manager", + retention_in_days=7, + tags=tags, +) + +ensemble_manager_log_group = aws.cloudwatch.LogGroup( + "ensemble_manager_logs", + name="/ecs/fund/ensemble-manager", + retention_in_days=7, + tags=tags, +) + +data_manager_task_definition = aws.ecs.TaskDefinition( + "data_manager_task", + family="data-manager", + cpu="256", + memory="512", + network_mode="awsvpc", + requires_compatibilities=["FARGATE"], + execution_role_arn=execution_role.arn, + task_role_arn=task_role.arn, + container_definitions=pulumi.Output.all( + data_manager_log_group.name, + data_manager_image_uri, + data_manager_secret.arn, + shared_secret.arn, + data_bucket.bucket, + ).apply( + lambda args: json.dumps( + [ + { + "name": "data-manager", + "image": args[1], + "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], + "environment": [ + { + "name": "MASSIVE_BASE_URL", + "value": "https://api.massive.com", + }, + { + "name": "AWS_S3_DATA_BUCKET_NAME", + "value": args[4], + }, + { + "name": "FUND_ENVIRONMENT", + "value": "production", + }, + { + "name": "RUST_LOG", + "value": "data_manager=info,tower_http=info", + }, + ], + "secrets": [ + { + "name": "MASSIVE_API_KEY", + "valueFrom": f"{args[2]}:MASSIVE_API_KEY::", + }, + { + "name": "SENTRY_DSN", + "valueFrom": f"{args[3]}:SENTRY_DSN::", + }, + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": args[0], + "awslogs-region": region, + "awslogs-stream-prefix": "data-manager", + }, + }, + "essential": True, + } + ], + sort_keys=True, + ) + ), + tags=tags, +) + +portfolio_manager_task_definition = aws.ecs.TaskDefinition( + "portfolio_manager_task", + family="portfolio-manager", + cpu="256", + memory="512", + network_mode="awsvpc", + requires_compatibilities=["FARGATE"], + execution_role_arn=execution_role.arn, + task_role_arn=task_role.arn, + container_definitions=pulumi.Output.all( + portfolio_manager_log_group.name, + service_discovery_namespace.name, + portfolio_manager_image_uri, + portfolio_manager_secret.arn, + shared_secret.arn, + parameters.uncertainty_threshold.value, + ).apply( + lambda args: json.dumps( + [ + { + "name": "portfolio-manager", + "image": args[2], + "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], + "environment": [ + { + "name": "FUND_DATAMANAGER_BASE_URL", + "value": f"http://data-manager.{args[1]}:8080", + }, + { + "name": "FUND_ENSEMBLE_MANAGER_BASE_URL", + "value": f"http://ensemble-manager.{args[1]}:8080", + }, + { + "name": "FUND_ENVIRONMENT", + "value": "production", + }, + { + "name": "FUND_UNCERTAINTY_THRESHOLD", + "value": args[5], + }, + ], + "secrets": [ + { + "name": "ALPACA_API_KEY_ID", + "valueFrom": f"{args[3]}:ALPACA_API_KEY_ID::", + }, + { + "name": "ALPACA_API_SECRET", + "valueFrom": f"{args[3]}:ALPACA_API_SECRET::", + }, + { + "name": "ALPACA_IS_PAPER", + "valueFrom": f"{args[3]}:ALPACA_IS_PAPER::", + }, + { + "name": "SENTRY_DSN", + "valueFrom": f"{args[4]}:SENTRY_DSN::", + }, + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": args[0], + "awslogs-region": region, + "awslogs-stream-prefix": "portfolio-manager", + }, + }, + "essential": True, + } + ], + sort_keys=True, + ) + ), + tags=tags, +) + +ensemble_manager_task_definition = aws.ecs.TaskDefinition( + "ensemble_manager_task", + family="ensemble-manager", + cpu="256", + memory="512", + network_mode="awsvpc", + requires_compatibilities=["FARGATE"], + execution_role_arn=execution_role.arn, + task_role_arn=task_role.arn, + container_definitions=pulumi.Output.all( + ensemble_manager_log_group.name, + service_discovery_namespace.name, + ensemble_manager_image_uri, + model_artifacts_bucket.bucket, + shared_secret.arn, + ).apply( + lambda args: json.dumps( + [ + { + "name": "ensemble-manager", + "image": args[2], + "portMappings": [{"containerPort": 8080, "protocol": "tcp"}], + "environment": [ + { + "name": "FUND_DATAMANAGER_BASE_URL", + "value": f"http://data-manager.{args[1]}:8080", + }, + { + "name": "AWS_S3_MODEL_ARTIFACTS_BUCKET_NAME", + "value": args[3], + }, + { + "name": "FUND_ENVIRONMENT", + "value": "production", + }, + { + "name": "DISABLE_DISK_CACHE", + "value": "1", + }, + ], + "secrets": [ + { + "name": "SENTRY_DSN", + "valueFrom": f"{args[4]}:SENTRY_DSN::", + }, + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": args[0], + "awslogs-region": region, + "awslogs-stream-prefix": "ensemble-manager", + }, + }, + "essential": True, + } + ], + sort_keys=True, + ) + ), + tags=tags, +) + +data_manager_sd_service = aws.servicediscovery.Service( + "data_manager_sd", + name="data-manager", + dns_config=aws.servicediscovery.ServiceDnsConfigArgs( + namespace_id=service_discovery_namespace.id, + dns_records=[ + aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") + ], + ), + tags=tags, +) + +portfolio_manager_sd_service = aws.servicediscovery.Service( + "portfolio_manager_sd", + name="portfolio-manager", + dns_config=aws.servicediscovery.ServiceDnsConfigArgs( + namespace_id=service_discovery_namespace.id, + dns_records=[ + aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") + ], + ), + tags=tags, +) + +ensemble_manager_sd_service = aws.servicediscovery.Service( + "ensemble_manager_sd", + name="ensemble-manager", + dns_config=aws.servicediscovery.ServiceDnsConfigArgs( + namespace_id=service_discovery_namespace.id, + dns_records=[ + aws.servicediscovery.ServiceDnsConfigDnsRecordArgs(ttl=10, type="A") + ], + ), + tags=tags, +) + +data_manager_service = aws.ecs.Service( + "data_manager_service", + name="fund-data-manager", + cluster=cluster.arn, + task_definition=data_manager_task_definition.arn, + desired_count=1, + launch_type="FARGATE", + network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( + subnets=[private_subnet_1.id, private_subnet_2.id], + security_groups=[ecs_security_group.id], + assign_public_ip=False, + ), + load_balancers=[ + aws.ecs.ServiceLoadBalancerArgs( + target_group_arn=data_manager_tg.arn, + container_name="data-manager", + container_port=8080, + ) + ], + service_registries=aws.ecs.ServiceServiceRegistriesArgs( + registry_arn=data_manager_sd_service.arn + ), + opts=pulumi.ResourceOptions(depends_on=[alb_listener]), + tags=tags, +) + +portfolio_manager_service = aws.ecs.Service( + "portfolio_manager_service", + name="fund-portfolio-manager", + cluster=cluster.arn, + task_definition=portfolio_manager_task_definition.arn, + desired_count=1, + launch_type="FARGATE", + network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( + subnets=[private_subnet_1.id, private_subnet_2.id], + security_groups=[ecs_security_group.id], + assign_public_ip=False, + ), + load_balancers=[ + aws.ecs.ServiceLoadBalancerArgs( + target_group_arn=portfolio_manager_tg.arn, + container_name="portfolio-manager", + container_port=8080, + ) + ], + service_registries=aws.ecs.ServiceServiceRegistriesArgs( + registry_arn=portfolio_manager_sd_service.arn + ), + opts=pulumi.ResourceOptions(depends_on=[alb_listener, data_manager_service]), + tags=tags, +) + +ensemble_manager_service = aws.ecs.Service( + "ensemble_manager_service", + name="fund-ensemble-manager", + cluster=cluster.arn, + task_definition=ensemble_manager_task_definition.arn, + desired_count=1, + launch_type="FARGATE", + network_configuration=aws.ecs.ServiceNetworkConfigurationArgs( + subnets=[private_subnet_1.id, private_subnet_2.id], + security_groups=[ecs_security_group.id], + assign_public_ip=False, + ), + load_balancers=[ + aws.ecs.ServiceLoadBalancerArgs( + target_group_arn=ensemble_manager_tg.arn, + container_name="ensemble-manager", + container_port=8080, + ) + ], + service_registries=aws.ecs.ServiceServiceRegistriesArgs( + registry_arn=ensemble_manager_sd_service.arn + ), + opts=pulumi.ResourceOptions(depends_on=[alb_listener, data_manager_service]), + tags=tags, +) diff --git a/infrastructure/config.py b/infrastructure/config.py new file mode 100644 index 000000000..3b06adc48 --- /dev/null +++ b/infrastructure/config.py @@ -0,0 +1,155 @@ +import json +from typing import cast + +import pulumi +import pulumi_aws as aws + + +def require_secret_config_object( + config: pulumi.Config, + key: str, +) -> pulumi.Output[dict[str, str]]: + config_full_key = config.full_key(key) + if not pulumi.runtime.is_config_secret(config_full_key): + message = f"Pulumi config '{key}' must be configured as a secret object." + raise ValueError(message) + + return cast( + "pulumi.Output[dict[str, str]]", + config.require_secret_object(key), + ) + + +def serialize_secret_config_object( + secret_values: dict[str, str], + config_key: str, + required_keys: set[str], +) -> str: + missing_secret_keys = sorted(required_keys.difference(secret_values)) + if missing_secret_keys: + message = ( + f"Pulumi config '{config_key}' is missing required keys: " + f"{', '.join(missing_secret_keys)}." + ) + raise ValueError(message) + + return json.dumps(secret_values, sort_keys=True) + + +stack_name = pulumi.get_stack() +if stack_name != "production": + message = "Only the production Pulumi stack is supported." + raise ValueError(message) + +stack_config = pulumi.Config("fund") +aws_config = pulumi.Config("aws") + +region = aws_config.require("region") + +random_suffix = stack_config.require_secret("randomSuffix") + +github_actions_role_name = stack_config.require("githubActionsRoleName") +github_repository = stack_config.require("githubRepository") +github_branch = stack_config.require("githubBranch") +github_workflow_files = cast( + "list[str]", + stack_config.require_object("githubWorkflowFiles"), +) +if not github_workflow_files: + message = ( + "Pulumi config 'githubWorkflowFiles' must include at least one workflow file." + ) + raise ValueError(message) + +budget_alert_email_addresses_full_key = stack_config.full_key( + "budgetAlertEmailAddresses" +) +if not pulumi.runtime.is_config_secret(budget_alert_email_addresses_full_key): + message = ( + "Pulumi config 'budgetAlertEmailAddresses' must be configured as a secret list." + ) + raise ValueError(message) +budget_alert_email_addresses = cast( + "list[str]", + stack_config.require_object("budgetAlertEmailAddresses"), +) +if not budget_alert_email_addresses: + message = ( + "Pulumi config 'budgetAlertEmailAddresses' must include at least one email " + "address." + ) + raise ValueError(message) + +monthly_budget_limit_usd = stack_config.require_float("monthlyBudgetLimitUsd") + +prefect_allowed_cidrs = cast( + "list[str]", + stack_config.require_object("prefectAllowedCidrs"), +) +if not prefect_allowed_cidrs: + message = ( + "Pulumi config 'prefectAllowedCidrs' must include at least one CIDR block." + ) + raise ValueError(message) + +prefect_allowed_ipv4_cidrs = [c for c in prefect_allowed_cidrs if ":" not in c] +prefect_allowed_ipv6_cidrs = [c for c in prefect_allowed_cidrs if ":" in c] + +training_notification_sender_email = stack_config.require_secret( + "trainingNotificationSenderEmail" +) +training_notification_recipient_emails = stack_config.require_secret( + "trainingNotificationRecipientEmails" +) + +data_manager_secret_name = stack_config.require_secret("datamanagerSecretName") +portfolio_manager_secret_name = stack_config.require_secret( + "portfoliomanagerSecretName" +) +shared_secret_name = stack_config.require_secret("sharedSecretName") + +data_manager_secret_values = require_secret_config_object( + stack_config, + "datamanagerSecretValue", +) +portfolio_manager_secret_values = require_secret_config_object( + stack_config, + "portfoliomanagerSecretValue", +) +shared_secret_values = require_secret_config_object( + stack_config, + "sharedSecretValue", +) + +github_oidc_audience_claim = "token.actions.githubusercontent.com:aud" +github_oidc_repository_claim = "token.actions.githubusercontent.com:repository" +github_oidc_ref_claim = "token.actions.githubusercontent.com:ref" +github_oidc_sub_claim = "token.actions.githubusercontent.com:sub" +github_oidc_workflow_ref_claim = "token.actions.githubusercontent.com:job_workflow_ref" + +github_workflow_refs = [ + ( + f"{github_repository}/.github/workflows/{github_workflow_file}" + f"@refs/heads/{github_branch}" + ) + for github_workflow_file in github_workflow_files +] + +current_identity = aws.get_caller_identity() + +account_id = current_identity.account_id + +availability_zone_a = f"{region}a" +availability_zone_b = f"{region}b" + +tags = { + "project": "fund", + "stack": stack_name, + "manager": "pulumi", +} + +github_oidc_provider_arn = pulumi.Output.concat( + "arn:aws:iam::", + account_id, + ":oidc-provider/token.actions.githubusercontent.com", +) diff --git a/infrastructure/iam.py b/infrastructure/iam.py new file mode 100644 index 000000000..199f1afd3 --- /dev/null +++ b/infrastructure/iam.py @@ -0,0 +1,570 @@ +import json +from secrets import ( + data_manager_secret, + data_manager_secret_name, + portfolio_manager_secret, + portfolio_manager_secret_name, + shared_secret, + shared_secret_name, +) + +import pulumi +import pulumi_aws as aws +from config import ( + account_id, + github_actions_role_name, + github_branch, + github_oidc_audience_claim, + github_oidc_provider_arn, + github_oidc_ref_claim, + github_oidc_repository_claim, + github_oidc_sub_claim, + github_oidc_workflow_ref_claim, + github_repository, + github_workflow_refs, + region, + tags, + training_notification_recipient_emails, + training_notification_sender_email, +) +from storage import data_bucket, model_artifacts_bucket + +github_actions_oidc_provider = aws.iam.OpenIdConnectProvider( + "github_actions_oidc_provider", + url="https://token.actions.githubusercontent.com", + client_id_lists=["sts.amazonaws.com"], + tags=tags, +) + +github_actions_infrastructure_policy = aws.iam.Policy( + "github_actions_infrastructure_policy", + name="fund-github-actions-infrastructure-policy", + description=( + "Least-privilege policy for GitHub Actions infrastructure deployments." + ), + policy=pulumi.Output.all( + data_manager_secret_name, + portfolio_manager_secret_name, + shared_secret_name, + github_oidc_provider_arn, + ).apply( + lambda args: json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + # These list/describe APIs are account-scoped and require wildcard + # resources. + { + "Sid": "ReadGlobalMetadata", + "Effect": "Allow", + "Action": [ + "sts:GetCallerIdentity", + "tag:GetResources", + "tag:GetTagKeys", + "tag:GetTagValues", + "iam:Get*", + "iam:List*", + "ec2:Describe*", + "ecs:Describe*", + "ecs:List*", + "elasticloadbalancing:Describe*", + "ecr:Describe*", + "ecr:ListTagsForResource", + "s3:GetBucketLocation", + "s3:ListAllMyBuckets", + "ssm:DescribeParameters", + "secretsmanager:ListSecrets", + "logs:Describe*", + "cloudwatch:Describe*", + "cloudwatch:Get*", + "sns:Get*", + "sns:List*", + "budgets:Describe*", + "budgets:ViewBudget", + "servicediscovery:Get*", + "servicediscovery:List*", + ], + "Resource": "*", + }, + # These control-plane APIs rely on generated identifiers and do not + # support practical resource-level scoping for stack create/update/ + # delete operations. + { + "Sid": "ManageEC2ECSELBBudgetsAndServiceDiscovery", + "Effect": "Allow", + "Action": [ + "ec2:*", + "ecs:*", + "elasticloadbalancing:*", + "budgets:*", + "servicediscovery:*", + ], + "Resource": "*", + }, + # CreateRepository/GetAuthorizationToken require wildcard resources. + { + "Sid": "CreateAndAuthenticateECRRepositories", + "Effect": "Allow", + "Action": [ + "ecr:CreateRepository", + "ecr:GetAuthorizationToken", + ], + "Resource": "*", + }, + { + "Sid": "ManageECRRepositories", + "Effect": "Allow", + "Action": "ecr:*", + "Resource": ( + f"arn:aws:ecr:{region}:{account_id}:repository/fund/*" + ), + }, + # CreateBucket requires wildcard resources. + { + "Sid": "CreateBuckets", + "Effect": "Allow", + "Action": "s3:CreateBucket", + "Resource": "*", + }, + { + "Sid": "ManageBuckets", + "Effect": "Allow", + "Action": "s3:*", + "Resource": [ + "arn:aws:s3:::fund-data-*", + "arn:aws:s3:::fund-data-*/*", + "arn:aws:s3:::fund-model-artifacts-*", + "arn:aws:s3:::fund-model-artifacts-*/*", + ], + }, + # CreateSecret requires wildcard resources before an ARN exists. + { + "Sid": "CreateSecrets", + "Effect": "Allow", + "Action": "secretsmanager:CreateSecret", + "Resource": "*", + }, + { + "Sid": "ManageConfiguredSecrets", + "Effect": "Allow", + "Action": "secretsmanager:*", + "Resource": [ + f"arn:aws:secretsmanager:{region}:{account_id}:secret:{args[0]}*", + f"arn:aws:secretsmanager:{region}:{account_id}:secret:{args[1]}*", + f"arn:aws:secretsmanager:{region}:{account_id}:secret:{args[2]}*", + ], + }, + { + "Sid": "ManageParameters", + "Effect": "Allow", + "Action": "ssm:*", + "Resource": ( + f"arn:aws:ssm:{region}:{account_id}:parameter/fund/*" + ), + }, + { + "Sid": "ManageLogGroups", + "Effect": "Allow", + "Action": "logs:*", + "Resource": [ + f"arn:aws:logs:{region}:{account_id}:log-group:/ecs/fund/*", + f"arn:aws:logs:{region}:{account_id}:log-group:/ecs/fund/*:*", + ], + }, + # Alarm mutation APIs require wildcard resources. + { + "Sid": "ManageAlarms", + "Effect": "Allow", + "Action": [ + "cloudwatch:DeleteAlarms", + "cloudwatch:ListTagsForResource", + "cloudwatch:PutMetricAlarm", + "cloudwatch:TagResource", + "cloudwatch:UntagResource", + ], + "Resource": "*", + }, + # CreateTopic requires wildcard resources. + { + "Sid": "CreateInfrastructureAlertsTopic", + "Effect": "Allow", + "Action": "sns:CreateTopic", + "Resource": "*", + }, + { + "Sid": "ManageInfrastructureAlertsTopic", + "Effect": "Allow", + "Action": "sns:*", + "Resource": [ + f"arn:aws:sns:{region}:{account_id}:fund-infrastructure-alerts", + f"arn:aws:sns:{region}:{account_id}:fund-infrastructure-alerts:*", + ], + }, + { + "Sid": "ManageSESIdentities", + "Effect": "Allow", + "Action": [ + "ses:CreateEmailIdentity", + "ses:DeleteEmailIdentity", + "ses:GetEmailIdentity", + "ses:TagResource", + "ses:UntagResource", + "ses:ListTagsForResource", + ], + "Resource": [ + f"arn:aws:ses:{region}:{account_id}:identity/*", + ], + }, + { + "Sid": "CreateGithubActionsOIDCProvider", + "Effect": "Allow", + "Action": "iam:CreateOpenIDConnectProvider", + "Resource": args[3], + }, + # CreateRole uses wildcard resources by API design. + { + "Sid": "CreateRoles", + "Effect": "Allow", + "Action": "iam:CreateRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:RoleName": [ + "fund-ecs-execution-role", + "fund-ecs-task-role", + github_actions_role_name, + ] + } + }, + }, + # CreatePolicy uses wildcard resources by API design. + { + "Sid": "CreatePolicies", + "Effect": "Allow", + "Action": "iam:CreatePolicy", + "Resource": "*", + "Condition": { + "StringLike": { + "iam:PolicyName": "fund-*", + } + }, + }, + # CreateServiceLinkedRole uses wildcard resources by API design. + { + "Sid": "CreateServiceLinkedRolesForStack", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": [ + "ecs.amazonaws.com", + "elasticloadbalancing.amazonaws.com", + ] + } + }, + }, + { + "Sid": "ManageRoles", + "Effect": "Allow", + "Action": [ + "iam:AttachRolePolicy", + "iam:DeleteRole", + "iam:DetachRolePolicy", + "iam:PassRole", + "iam:TagRole", + "iam:UntagRole", + "iam:UpdateAssumeRolePolicy", + ], + "Resource": [ + f"arn:aws:iam::{account_id}:role/fund-ecs-execution-role", + f"arn:aws:iam::{account_id}:role/fund-ecs-task-role", + f"arn:aws:iam::{account_id}:role/{github_actions_role_name}", + ], + "Condition": { + "ArnLikeIfExists": { + "iam:PolicyARN": [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + f"arn:aws:iam::{account_id}:policy/fund-*", + ] + }, + "StringLikeIfExists": { + "iam:PassedToService": [ + "ecs-tasks.amazonaws.com", + "ecs.amazonaws.com", + ] + }, + }, + }, + { + "Sid": "ManageInlineRolePolicies", + "Effect": "Allow", + "Action": [ + "iam:DeleteRolePolicy", + "iam:PutRolePolicy", + ], + "Resource": [ + f"arn:aws:iam::{account_id}:role/fund-ecs-execution-role", + f"arn:aws:iam::{account_id}:role/fund-ecs-task-role", + ], + "Condition": { + "StringEquals": { + "iam:PolicyName": [ + "fund-ecs-execution-role-secrets-policy", + "fund-ecs-task-role-s3-policy", + "fund-ecs-task-role-ssm-policy", + "fund-ecs-task-role-ses-policy", + ] + } + }, + }, + { + "Sid": "ManagePolicies", + "Effect": "Allow", + "Action": [ + "iam:CreatePolicyVersion", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:SetDefaultPolicyVersion", + "iam:TagPolicy", + "iam:UntagPolicy", + ], + "Resource": f"arn:aws:iam::{account_id}:policy/fund-*", + }, + { + "Sid": "ManageGithubActionsOIDCProvider", + "Effect": "Allow", + "Action": [ + "iam:AddClientIDToOpenIDConnectProvider", + "iam:DeleteOpenIDConnectProvider", + "iam:RemoveClientIDFromOpenIDConnectProvider", + "iam:TagOpenIDConnectProvider", + "iam:UntagOpenIDConnectProvider", + "iam:UpdateOpenIDConnectProviderThumbprint", + ], + "Resource": args[3], + }, + # Service-linked role teardown APIs are wildcard-resource only. + { + "Sid": "DeleteServiceLinkedRoles", + "Effect": "Allow", + "Action": [ + "iam:DeleteServiceLinkedRole", + "iam:GetServiceLinkedRoleDeletionStatus", + ], + "Resource": "*", + "Condition": { + "StringLikeIfExists": { + "iam:AWSServiceName": [ + "ecs.amazonaws.com", + "elasticloadbalancing.amazonaws.com", + ] + } + }, + }, + ], + }, + sort_keys=True, + ) + ), + opts=pulumi.ResourceOptions(retain_on_delete=True), + tags=tags, +) + +github_actions_infrastructure_role = aws.iam.Role( + "github_actions_infrastructure_role", + name=github_actions_role_name, + assume_role_policy=github_actions_oidc_provider.arn.apply( + lambda github_actions_oidc_provider_arn: json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": github_actions_oidc_provider_arn, + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + github_oidc_audience_claim: "sts.amazonaws.com", + github_oidc_repository_claim: github_repository, + github_oidc_ref_claim: f"refs/heads/{github_branch}", + github_oidc_workflow_ref_claim: github_workflow_refs, + }, + "StringLike": { + github_oidc_sub_claim: f"repo:{github_repository}:*", + }, + }, + } + ], + }, + sort_keys=True, + ) + ), + managed_policy_arns=[github_actions_infrastructure_policy.arn], + opts=pulumi.ResourceOptions(retain_on_delete=True), + tags=tags, +) + +# IAM Role for ECS to perform infrastructure tasks +execution_role = aws.iam.Role( + "execution_role", + name="fund-ecs-execution-role", + assume_role_policy=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": {"Service": "ecs-tasks.amazonaws.com"}, + } + ], + }, + sort_keys=True, + ), + tags=tags, +) + +aws.iam.RolePolicyAttachment( + "execution_role_policy", + role=execution_role.name, + policy_arn="arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", +) + +# Allow ECS tasks to read secrets from Secrets Manager +aws.iam.RolePolicy( + "execution_role_secrets_policy", + name="fund-ecs-execution-role-secrets-policy", + role=execution_role.id, + policy=pulumi.Output.all( + data_manager_secret.arn, + portfolio_manager_secret.arn, + shared_secret.arn, + ).apply( + lambda args: json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["secretsmanager:GetSecretValue"], + "Resource": [args[0], args[1], args[2]], + } + ], + }, + sort_keys=True, + ) + ), +) + + +# IAM Role for ECS tasks to access AWS resources +task_role = aws.iam.Role( + "task_role", + name="fund-ecs-task-role", + assume_role_policy=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": {"Service": "ecs-tasks.amazonaws.com"}, + } + ], + }, + sort_keys=True, + ), + tags=tags, +) + +aws.iam.RolePolicy( + "task_role_s3_policy", + name="fund-ecs-task-role-s3-policy", + role=task_role.id, + policy=pulumi.Output.all(data_bucket.arn, model_artifacts_bucket.arn).apply( + lambda args: json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"], + "Resource": [ + args[0], + f"{args[0]}/*", + args[1], + f"{args[1]}/*", + ], + } + ], + }, + sort_keys=True, + ) + ), +) + +aws.iam.RolePolicy( + "task_role_ssm_policy", + name="fund-ecs-task-role-ssm-policy", + role=task_role.id, + policy=json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["ssm:GetParameter", "ssm:GetParameters"], + "Resource": f"arn:aws:ssm:{region}:{account_id}:parameter/fund/*", + } + ], + }, + sort_keys=True, + ), +) + +# SES Email Identity for training notifications +training_notification_email_identity = aws.ses.EmailIdentity( + "training_notification_email_identity", + email=training_notification_sender_email, +) + +training_notification_sender_email_parameter = aws.ssm.Parameter( + "training_notification_sender_email_parameter", + name="/fund/training/training_notification_sender_email", + type="SecureString", + value=training_notification_sender_email, + tags=tags, +) + +training_notification_recipients_parameter = aws.ssm.Parameter( + "training_notification_recipients_parameter", + name="/fund/training/training_notification_recipients", + type="SecureString", + value=training_notification_recipient_emails, + tags=tags, +) + +# Allow ECS tasks to send emails via SES +aws.iam.RolePolicy( + "task_role_ses_policy", + name="fund-ecs-task-role-ses-policy", + role=task_role.id, + policy=training_notification_email_identity.arn.apply( + lambda identity_arn: json.dumps( + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["ses:SendEmail", "ses:SendRawEmail"], + "Resource": identity_arn, + } + ], + }, + sort_keys=True, + ) + ), +) diff --git a/infrastructure/networking.py b/infrastructure/networking.py new file mode 100644 index 000000000..e429e492a --- /dev/null +++ b/infrastructure/networking.py @@ -0,0 +1,304 @@ +import pulumi +import pulumi_aws as aws +from config import ( + availability_zone_a, + availability_zone_b, + prefect_allowed_ipv4_cidrs, + prefect_allowed_ipv6_cidrs, + region, + tags, +) +from notifications import infrastructure_alerts_topic + +vpc = aws.ec2.Vpc( + "vpc", + cidr_block="10.0.0.0/16", + enable_dns_hostnames=True, + enable_dns_support=True, + tags=tags, +) + +# Internet Gateway for public subnets +igw = aws.ec2.InternetGateway( + "igw", + vpc_id=vpc.id, + tags=tags, +) + +# Public subnets for ALB +public_subnet_1 = aws.ec2.Subnet( + "public_subnet_1", + vpc_id=vpc.id, + cidr_block="10.0.1.0/24", + availability_zone=availability_zone_a, + map_public_ip_on_launch=True, + tags=tags, +) + +public_subnet_2 = aws.ec2.Subnet( + "public_subnet_2", + vpc_id=vpc.id, + cidr_block="10.0.2.0/24", + availability_zone=availability_zone_b, + map_public_ip_on_launch=True, + tags=tags, +) + +# Private subnets for ECS tasks +private_subnet_1 = aws.ec2.Subnet( + "private_subnet_1", + vpc_id=vpc.id, + cidr_block="10.0.3.0/24", + availability_zone=availability_zone_a, + tags=tags, +) + +private_subnet_2 = aws.ec2.Subnet( + "private_subnet_2", + vpc_id=vpc.id, + cidr_block="10.0.4.0/24", + availability_zone=availability_zone_b, + tags=tags, +) + +public_route_table = aws.ec2.RouteTable( + "public_route_table", + vpc_id=vpc.id, + tags=tags, +) + +aws.ec2.Route( + "public_internet_route", + route_table_id=public_route_table.id, + destination_cidr_block="0.0.0.0/0", + gateway_id=igw.id, +) + +aws.ec2.RouteTableAssociation( + "public_subnet_1_rta", + subnet_id=public_subnet_1.id, + route_table_id=public_route_table.id, +) + +aws.ec2.RouteTableAssociation( + "public_subnet_2_rta", + subnet_id=public_subnet_2.id, + route_table_id=public_route_table.id, +) + +eip = aws.ec2.Eip( + "nat_elastic_ip", + domain="vpc", + tags=tags, +) + +# NAT Gateway in public subnet for private subnet outbound traffic +nat = aws.ec2.NatGateway( + "nat_gateway", + subnet_id=public_subnet_1.id, + allocation_id=eip.id, + tags=tags, +) + +aws.cloudwatch.MetricAlarm( + "nat_gateway_bytes_out_to_destination_alarm", + name="fund-nat-gateway-bytes-out-to-destination", + alarm_description=( + "Triggers when NAT gateway outbound traffic exceeds 500 MB per hour for " + "2 consecutive hours." + ), + namespace="AWS/NATGateway", + metric_name="BytesOutToDestination", + statistic="Sum", + period=3600, + evaluation_periods=2, + threshold=500_000_000, + comparison_operator="GreaterThanThreshold", + treat_missing_data="notBreaching", + dimensions={"NatGatewayId": nat.id}, + alarm_actions=[infrastructure_alerts_topic.arn], + ok_actions=[infrastructure_alerts_topic.arn], + tags=tags, +) + +private_route_table = aws.ec2.RouteTable( + "private_route_table", + vpc_id=vpc.id, + tags=tags, +) + +aws.ec2.Route( + "nat_route", + route_table_id=private_route_table.id, + destination_cidr_block="0.0.0.0/0", + nat_gateway_id=nat.id, +) + +aws.ec2.RouteTableAssociation( + "private_subnet_1_rta", + subnet_id=private_subnet_1.id, + route_table_id=private_route_table.id, +) + +aws.ec2.RouteTableAssociation( + "private_subnet_2_rta", + subnet_id=private_subnet_2.id, + route_table_id=private_route_table.id, +) + +alb_security_group = aws.ec2.SecurityGroup( + "alb_sg", + name="fund-alb", + vpc_id=vpc.id, + description="Security group for ALB", + ingress=[ + aws.ec2.SecurityGroupIngressArgs( + protocol="tcp", + from_port=80, + to_port=80, + cidr_blocks=["0.0.0.0/0"], + description="Allow HTTP", + ), + aws.ec2.SecurityGroupIngressArgs( + protocol="tcp", + from_port=443, + to_port=443, + cidr_blocks=["0.0.0.0/0"], + description="Allow HTTPS", + ), + *( + [ + aws.ec2.SecurityGroupIngressArgs( + protocol="tcp", + from_port=4200, + to_port=4200, + cidr_blocks=prefect_allowed_ipv4_cidrs, + description="Allow Prefect dashboard from team IPv4", + ), + ] + if prefect_allowed_ipv4_cidrs + else [] + ), + *( + [ + aws.ec2.SecurityGroupIngressArgs( + protocol="tcp", + from_port=4200, + to_port=4200, + ipv6_cidr_blocks=prefect_allowed_ipv6_cidrs, + description="Allow Prefect dashboard from team IPv6", + ), + ] + if prefect_allowed_ipv6_cidrs + else [] + ), + ], + egress=[ + aws.ec2.SecurityGroupEgressArgs( + protocol="-1", + from_port=0, + to_port=0, + cidr_blocks=["0.0.0.0/0"], + description="Allow all outbound", + ) + ], + tags=tags, +) + +ecs_security_group = aws.ec2.SecurityGroup( + "ecs_sg", + name="fund-ecs-tasks", + vpc_id=vpc.id, + description="Security group for ECS tasks", + tags=tags, +) + +# Allow ALB to reach ECS tasks on port 8080 +aws.ec2.SecurityGroupRule( + "ecs_from_alb", + type="ingress", + security_group_id=ecs_security_group.id, + source_security_group_id=alb_security_group.id, + protocol="tcp", + from_port=8080, + to_port=8080, + description="Allow ALB traffic", +) + +# Allow ECS tasks to communicate with each other +aws.ec2.SecurityGroupRule( + "ecs_self_ingress", + type="ingress", + security_group_id=ecs_security_group.id, + source_security_group_id=ecs_security_group.id, + protocol="tcp", + from_port=8080, + to_port=8080, + description="Allow inter-service communication", +) + +# Allow all outbound traffic from ECS tasks +aws.ec2.SecurityGroupRule( + "ecs_egress", + type="egress", + security_group_id=ecs_security_group.id, + protocol="-1", + from_port=0, + to_port=0, + cidr_blocks=["0.0.0.0/0"], + description="Allow all outbound", +) + +# VPC Endpoints Security Group +vpc_endpoints_security_group = aws.ec2.SecurityGroup( + "vpc_endpoints_sg", + name="fund-vpc-endpoints", + vpc_id=vpc.id, + description="Security group for VPC endpoints", + tags=tags, +) + +aws.ec2.SecurityGroupRule( + "vpc_endpoints_ingress", + type="ingress", + security_group_id=vpc_endpoints_security_group.id, + source_security_group_id=ecs_security_group.id, + protocol="tcp", + from_port=443, + to_port=443, + description="Allow HTTPS from ECS tasks", +) + +# S3 Gateway Endpoint +aws.ec2.VpcEndpoint( + "s3_gateway_endpoint", + vpc_id=vpc.id, + service_name=pulumi.Output.concat("com.amazonaws.", region, ".s3"), + vpc_endpoint_type="Gateway", + route_table_ids=[private_route_table.id], + tags=tags, +) + +# ECR API Interface Endpoint +aws.ec2.VpcEndpoint( + "ecr_api_endpoint", + vpc_id=vpc.id, + service_name=pulumi.Output.concat("com.amazonaws.", region, ".ecr.api"), + vpc_endpoint_type="Interface", + subnet_ids=[private_subnet_1.id, private_subnet_2.id], + security_group_ids=[vpc_endpoints_security_group.id], + private_dns_enabled=True, + tags=tags, +) + +# ECR DKR Interface Endpoint +aws.ec2.VpcEndpoint( + "ecr_dkr_endpoint", + vpc_id=vpc.id, + service_name=pulumi.Output.concat("com.amazonaws.", region, ".ecr.dkr"), + vpc_endpoint_type="Interface", + subnet_ids=[private_subnet_1.id, private_subnet_2.id], + security_group_ids=[vpc_endpoints_security_group.id], + private_dns_enabled=True, + tags=tags, +) diff --git a/infrastructure/notifications.py b/infrastructure/notifications.py new file mode 100644 index 000000000..e94143c25 --- /dev/null +++ b/infrastructure/notifications.py @@ -0,0 +1,50 @@ +import pulumi_aws as aws +from config import ( + account_id, + budget_alert_email_addresses, + monthly_budget_limit_usd, + tags, +) + +infrastructure_alerts_topic = aws.sns.Topic( + "infrastructure_alerts_topic", + name="fund-infrastructure-alerts", + tags=tags, +) + +for notification_email_index, notification_email_address in enumerate( + budget_alert_email_addresses, + start=1, +): + aws.sns.TopicSubscription( + f"infrastructure_alert_email_subscription_{notification_email_index}", + topic=infrastructure_alerts_topic.arn, + protocol="email", + endpoint=notification_email_address, + ) + +aws.budgets.Budget( + "production_cost_budget", + account_id=account_id, + name="fund-monthly-cost", + budget_type="COST", + time_unit="MONTHLY", + limit_amount=f"{monthly_budget_limit_usd:.2f}", + limit_unit="USD", + notifications=[ + aws.budgets.BudgetNotificationArgs( + comparison_operator="GREATER_THAN", + notification_type="ACTUAL", + threshold=monthly_budget_limit_usd, + threshold_type="ABSOLUTE_VALUE", + subscriber_email_addresses=budget_alert_email_addresses, + ), + aws.budgets.BudgetNotificationArgs( + comparison_operator="GREATER_THAN", + notification_type="FORECASTED", + threshold=monthly_budget_limit_usd, + threshold_type="ABSOLUTE_VALUE", + subscriber_email_addresses=budget_alert_email_addresses, + ), + ], +) diff --git a/infrastructure/secrets.py b/infrastructure/secrets.py new file mode 100644 index 000000000..02d49cf24 --- /dev/null +++ b/infrastructure/secrets.py @@ -0,0 +1,68 @@ +import pulumi_aws as aws +from config import ( + data_manager_secret_name, + data_manager_secret_values, + portfolio_manager_secret_name, + portfolio_manager_secret_values, + serialize_secret_config_object, + shared_secret_name, + shared_secret_values, + tags, +) + +data_manager_secret = aws.secretsmanager.Secret( + "data_manager_secret", + name=data_manager_secret_name, + recovery_window_in_days=0, + tags=tags, +) + +portfolio_manager_secret = aws.secretsmanager.Secret( + "portfolio_manager_secret", + name=portfolio_manager_secret_name, + recovery_window_in_days=0, + tags=tags, +) + +shared_secret = aws.secretsmanager.Secret( + "shared_secret", + name=shared_secret_name, + recovery_window_in_days=0, + tags=tags, +) + +aws.secretsmanager.SecretVersion( + "data_manager_secret_version", + secret_id=data_manager_secret.id, + secret_string=data_manager_secret_values.apply( + lambda values: serialize_secret_config_object( + values, + "datamanagerSecretValue", + {"MASSIVE_API_KEY"}, + ) + ), +) + +aws.secretsmanager.SecretVersion( + "portfolio_manager_secret_version", + secret_id=portfolio_manager_secret.id, + secret_string=portfolio_manager_secret_values.apply( + lambda values: serialize_secret_config_object( + values, + "portfoliomanagerSecretValue", + {"ALPACA_API_KEY_ID", "ALPACA_API_SECRET", "ALPACA_IS_PAPER"}, + ) + ), +) + +aws.secretsmanager.SecretVersion( + "shared_secret_version", + secret_id=shared_secret.id, + secret_string=shared_secret_values.apply( + lambda values: serialize_secret_config_object( + values, + "sharedSecretValue", + {"SENTRY_DSN"}, + ) + ), +) diff --git a/infrastructure/storage.py b/infrastructure/storage.py new file mode 100644 index 000000000..952f5cbab --- /dev/null +++ b/infrastructure/storage.py @@ -0,0 +1,182 @@ +import pulumi +import pulumi_aws as aws +from config import random_suffix, tags + +# S3 Data Bucket for storing equity bars, predictions, portfolios +# alias: migrated from aws:s3/bucket:Bucket to aws:s3/bucketV2:BucketV2 +data_bucket = aws.s3.BucketV2( + "data_bucket", + bucket=pulumi.Output.concat("fund-data-", random_suffix), + opts=pulumi.ResourceOptions( + retain_on_delete=True, + aliases=[pulumi.Alias(type_="aws:s3/bucket:Bucket")], + ), + tags=tags, +) + +aws.s3.BucketServerSideEncryptionConfiguration( + "data_bucket_encryption", + bucket=data_bucket.id, + rules=[ + aws.s3.BucketServerSideEncryptionConfigurationRuleArgs( + apply_server_side_encryption_by_default=aws.s3.BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefaultArgs( + sse_algorithm="AES256", + ), + ) + ], + opts=pulumi.ResourceOptions(retain_on_delete=True), +) + +aws.s3.BucketPublicAccessBlock( + "data_bucket_public_access_block", + bucket=data_bucket.id, + block_public_acls=True, + block_public_policy=True, + ignore_public_acls=True, + restrict_public_buckets=True, + opts=pulumi.ResourceOptions(retain_on_delete=True), +) + +aws.s3.BucketVersioning( + "data_bucket_versioning", + bucket=data_bucket.id, + versioning_configuration=aws.s3.BucketVersioningVersioningConfigurationArgs( + status="Enabled", + ), + opts=pulumi.ResourceOptions(retain_on_delete=True), +) + +# S3 Model Artifacts Bucket for storing trained model weights and checkpoints +# alias: migrated from aws:s3/bucket:Bucket to aws:s3/bucketV2:BucketV2 +model_artifacts_bucket = aws.s3.BucketV2( + "model_artifacts_bucket", + bucket=pulumi.Output.concat("fund-model-artifacts-", random_suffix), + opts=pulumi.ResourceOptions( + retain_on_delete=True, + aliases=[pulumi.Alias(type_="aws:s3/bucket:Bucket")], + ), + tags=tags, +) + +aws.s3.BucketServerSideEncryptionConfiguration( + "model_artifacts_bucket_encryption", + bucket=model_artifacts_bucket.id, + rules=[ + aws.s3.BucketServerSideEncryptionConfigurationRuleArgs( + apply_server_side_encryption_by_default=aws.s3.BucketServerSideEncryptionConfigurationRuleApplyServerSideEncryptionByDefaultArgs( + sse_algorithm="AES256", + ), + ) + ], + opts=pulumi.ResourceOptions(retain_on_delete=True), +) + +aws.s3.BucketPublicAccessBlock( + "model_artifacts_bucket_public_access_block", + bucket=model_artifacts_bucket.id, + block_public_acls=True, + block_public_policy=True, + ignore_public_acls=True, + restrict_public_buckets=True, + opts=pulumi.ResourceOptions(retain_on_delete=True), +) + +aws.s3.BucketVersioning( + "model_artifacts_bucket_versioning", + bucket=model_artifacts_bucket.id, + versioning_configuration=aws.s3.BucketVersioningVersioningConfigurationArgs( + status="Enabled", + ), + opts=pulumi.ResourceOptions(retain_on_delete=True), +) + +# ECR Repositories - these must exist before images can be pushed +# force_delete allows repositories containing images to be deleted on stack teardown. +# If image rebuild and push times become prohibitive on daily down/up cycles, switch to +# retain_on_delete=True and add pulumi import statements to the maskfile up command. +data_manager_repository = aws.ecr.Repository( + "data_manager_repository", + name="fund/data-manager-server", + image_tag_mutability="MUTABLE", + force_delete=True, + image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( + scan_on_push=True, + ), + tags=tags, +) + +portfolio_manager_repository = aws.ecr.Repository( + "portfolio_manager_repository", + name="fund/portfolio-manager-server", + image_tag_mutability="MUTABLE", + force_delete=True, + image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( + scan_on_push=True, + ), + tags=tags, +) + +ensemble_manager_repository = aws.ecr.Repository( + "ensemble_manager_repository", + name="fund/ensemble-manager-server", + image_tag_mutability="MUTABLE", + force_delete=True, + image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( + scan_on_push=True, + ), + tags=tags, +) + +tide_trainer_repository = aws.ecr.Repository( + "tide_trainer_repository", + name="fund/tide-trainer", + image_tag_mutability="MUTABLE", + force_delete=True, + image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( + scan_on_push=True, + ), + tags=tags, +) + +training_server_repository = aws.ecr.Repository( + "training_server_repository", + name="fund/training-server", + image_tag_mutability="MUTABLE", + force_delete=True, + image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( + scan_on_push=True, + ), + tags=tags, +) + +training_worker_repository = aws.ecr.Repository( + "training_worker_repository", + name="fund/training-worker", + image_tag_mutability="MUTABLE", + force_delete=True, + image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( + scan_on_push=True, + ), + tags=tags, +) + +# Generate image URIs - these will be used in task definitions +# For initial deployment, use a placeholder that will be updated when images are pushed +data_manager_image_uri = data_manager_repository.repository_url.apply( + lambda url: f"{url}:latest" +) +portfolio_manager_image_uri = portfolio_manager_repository.repository_url.apply( + lambda url: f"{url}:latest" +) +ensemble_manager_image_uri = ensemble_manager_repository.repository_url.apply( + lambda url: f"{url}:latest" +) +tide_trainer_image_uri = tide_trainer_repository.repository_url.apply( + lambda url: f"{url}:latest" +) +training_server_image_uri = training_server_repository.repository_url.apply( + lambda url: f"{url}:latest" +) +training_worker_image_uri = training_worker_repository.repository_url.apply( + lambda url: f"{url}:latest" +) diff --git a/libraries/python/tests/test_infrastructure_config.py b/libraries/python/tests/test_infrastructure_config.py new file mode 100644 index 000000000..9148e9c4d --- /dev/null +++ b/libraries/python/tests/test_infrastructure_config.py @@ -0,0 +1,54 @@ +import re +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +INFRASTRUCTURE_CONFIG_PATH = REPOSITORY_ROOT / "infrastructure" / "config.py" +PRODUCTION_STACK_CONFIG_PATH = ( + REPOSITORY_ROOT / "infrastructure" / "Pulumi.production.yaml" +) + + +def load_infrastructure_config() -> str: + return INFRASTRUCTURE_CONFIG_PATH.read_text(encoding="utf-8") + + +def load_production_stack_config() -> str: + return PRODUCTION_STACK_CONFIG_PATH.read_text(encoding="utf-8") + + +def test_production_stack_config_stores_region_as_plaintext() -> None: + production_stack_config = load_production_stack_config() + + assert "aws:region:" in production_stack_config + assert re.search(r"aws:region:\s+[a-z]+-[a-z]+-\d+", production_stack_config) + assert not re.search(r"aws:region:\s+secure:", production_stack_config) + + +def test_production_stack_config_stores_budget_alert_emails_as_secret() -> None: + production_stack_config = load_production_stack_config() + + assert "fund:budgetAlertEmailAddresses:" in production_stack_config + assert re.search( + r"fund:budgetAlertEmailAddresses:\s+secure:", production_stack_config + ) + + +def test_config_contains_oidc_claim_constants() -> None: + infrastructure_config = load_infrastructure_config() + + assert ( + 'github_oidc_repository_claim = "token.actions.githubusercontent.com:' + 'repository"' in infrastructure_config + ) + assert ( + 'github_oidc_ref_claim = "token.actions.githubusercontent.com:ref"' + in infrastructure_config + ) + assert ( + 'github_oidc_workflow_ref_claim = "token.actions.githubusercontent.com:' + 'job_workflow_ref"' in infrastructure_config + ) + assert ( + 'github_oidc_sub_claim = "token.actions.githubusercontent.com:sub"' + in infrastructure_config + ) diff --git a/libraries/python/tests/test_infrastructure_configuration.py b/libraries/python/tests/test_infrastructure_configuration.py deleted file mode 100644 index cfe5809bd..000000000 --- a/libraries/python/tests/test_infrastructure_configuration.py +++ /dev/null @@ -1,102 +0,0 @@ -import re -from pathlib import Path - -REPOSITORY_ROOT = Path(__file__).resolve().parents[3] -INFRASTRUCTURE_ENTRYPOINT_PATH = REPOSITORY_ROOT / "infrastructure" / "__main__.py" -PRODUCTION_STACK_CONFIG_PATH = ( - REPOSITORY_ROOT / "infrastructure" / "Pulumi.production.yaml" -) - - -def load_infrastructure_entrypoint() -> str: - return INFRASTRUCTURE_ENTRYPOINT_PATH.read_text(encoding="utf-8") - - -def load_production_stack_config() -> str: - return PRODUCTION_STACK_CONFIG_PATH.read_text(encoding="utf-8") - - -def test_production_stack_config_stores_region_as_plaintext() -> None: - production_stack_config = load_production_stack_config() - - assert "aws:region:" in production_stack_config - assert re.search(r"aws:region:\s+[a-z]+-[a-z]+-\d+", production_stack_config) - assert not re.search(r"aws:region:\s+secure:", production_stack_config) - - -def test_production_stack_config_stores_budget_alert_emails_as_secret() -> None: - production_stack_config = load_production_stack_config() - - assert "fund:budgetAlertEmailAddresses:" in production_stack_config - assert re.search( - r"fund:budgetAlertEmailAddresses:\s+secure:", production_stack_config - ) - - -def test_infrastructure_entrypoint_contains_oidc_claim_constraints() -> None: - infrastructure_entrypoint = load_infrastructure_entrypoint() - - assert ( - 'github_oidc_repository_claim = "token.actions.githubusercontent.com:' - 'repository"' in infrastructure_entrypoint - ) - assert ( - 'github_oidc_ref_claim = "token.actions.githubusercontent.com:ref"' - in infrastructure_entrypoint - ) - assert ( - 'github_oidc_workflow_ref_claim = "token.actions.githubusercontent.com:' - 'job_workflow_ref"' in infrastructure_entrypoint - ) - assert ( - 'github_oidc_sub_claim = "token.actions.githubusercontent.com:sub"' - in infrastructure_entrypoint - ) - - -def test_infrastructure_entrypoint_does_not_use_administrator_access() -> None: - infrastructure_entrypoint = load_infrastructure_entrypoint() - - assert "AdministratorAccess" not in infrastructure_entrypoint - - -def test_infrastructure_entrypoint_contains_nat_gateway_baseline_alarm() -> None: - infrastructure_entrypoint = load_infrastructure_entrypoint() - - assert '"nat_gateway_bytes_out_to_destination_alarm"' in infrastructure_entrypoint - assert 'metric_name="BytesOutToDestination"' in infrastructure_entrypoint - assert "threshold=500_000_000" in infrastructure_entrypoint - assert "period=3600" in infrastructure_entrypoint - assert "evaluation_periods=2" in infrastructure_entrypoint - - -def test_infrastructure_entrypoint_contains_s3_bucket_encryption_resources() -> None: - infrastructure_entrypoint = load_infrastructure_entrypoint() - - assert '"data_bucket_encryption"' in infrastructure_entrypoint - assert '"model_artifacts_bucket_encryption"' in infrastructure_entrypoint - - -def test_infrastructure_entrypoint_attaches_custom_github_actions_policy() -> None: - infrastructure_entrypoint = load_infrastructure_entrypoint() - - assert '"github_actions_infrastructure_policy"' in infrastructure_entrypoint - assert ( - "managed_policy_arns=[github_actions_infrastructure_policy.arn]" - in infrastructure_entrypoint - ) - - -def test_infrastructure_entrypoint_contains_s3_public_access_block_resources() -> None: - infrastructure_entrypoint = load_infrastructure_entrypoint() - - assert '"data_bucket_public_access_block"' in infrastructure_entrypoint - assert '"model_artifacts_bucket_public_access_block"' in infrastructure_entrypoint - - -def test_infrastructure_entrypoint_scopes_oidc_provider_creation_statement() -> None: - infrastructure_entrypoint = load_infrastructure_entrypoint() - - assert '"CreateGithubActionsOIDCProvider"' in infrastructure_entrypoint - assert '"Resource": args[3]' in infrastructure_entrypoint - assert '"CreateIamResourcesForOscmStack"' not in infrastructure_entrypoint diff --git a/libraries/python/tests/test_infrastructure_iam.py b/libraries/python/tests/test_infrastructure_iam.py new file mode 100644 index 000000000..6e9b0398f --- /dev/null +++ b/libraries/python/tests/test_infrastructure_iam.py @@ -0,0 +1,32 @@ +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +INFRASTRUCTURE_IAM_PATH = REPOSITORY_ROOT / "infrastructure" / "iam.py" + + +def load_infrastructure_iam() -> str: + return INFRASTRUCTURE_IAM_PATH.read_text(encoding="utf-8") + + +def test_iam_does_not_use_administrator_access() -> None: + infrastructure_iam = load_infrastructure_iam() + + assert "AdministratorAccess" not in infrastructure_iam + + +def test_iam_attaches_custom_github_actions_policy() -> None: + infrastructure_iam = load_infrastructure_iam() + + assert '"github_actions_infrastructure_policy"' in infrastructure_iam + assert ( + "managed_policy_arns=[github_actions_infrastructure_policy.arn]" + in infrastructure_iam + ) + + +def test_iam_scopes_oidc_provider_creation_statement() -> None: + infrastructure_iam = load_infrastructure_iam() + + assert '"CreateGithubActionsOIDCProvider"' in infrastructure_iam + assert '"Resource": args[3]' in infrastructure_iam + assert '"CreateIamResourcesForOscmStack"' not in infrastructure_iam diff --git a/libraries/python/tests/test_infrastructure_networking.py b/libraries/python/tests/test_infrastructure_networking.py new file mode 100644 index 000000000..d39af7761 --- /dev/null +++ b/libraries/python/tests/test_infrastructure_networking.py @@ -0,0 +1,18 @@ +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +INFRASTRUCTURE_NETWORKING_PATH = REPOSITORY_ROOT / "infrastructure" / "networking.py" + + +def load_infrastructure_networking() -> str: + return INFRASTRUCTURE_NETWORKING_PATH.read_text(encoding="utf-8") + + +def test_networking_contains_nat_gateway_baseline_alarm() -> None: + infrastructure_networking = load_infrastructure_networking() + + assert '"nat_gateway_bytes_out_to_destination_alarm"' in infrastructure_networking + assert 'metric_name="BytesOutToDestination"' in infrastructure_networking + assert "threshold=500_000_000" in infrastructure_networking + assert "period=3600" in infrastructure_networking + assert "evaluation_periods=2" in infrastructure_networking diff --git a/libraries/python/tests/test_infrastructure_storage.py b/libraries/python/tests/test_infrastructure_storage.py new file mode 100644 index 000000000..264cfb85b --- /dev/null +++ b/libraries/python/tests/test_infrastructure_storage.py @@ -0,0 +1,22 @@ +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[3] +INFRASTRUCTURE_STORAGE_PATH = REPOSITORY_ROOT / "infrastructure" / "storage.py" + + +def load_infrastructure_storage() -> str: + return INFRASTRUCTURE_STORAGE_PATH.read_text(encoding="utf-8") + + +def test_storage_contains_s3_bucket_encryption_resources() -> None: + infrastructure_storage = load_infrastructure_storage() + + assert '"data_bucket_encryption"' in infrastructure_storage + assert '"model_artifacts_bucket_encryption"' in infrastructure_storage + + +def test_storage_contains_s3_public_access_block_resources() -> None: + infrastructure_storage = load_infrastructure_storage() + + assert '"data_bucket_public_access_block"' in infrastructure_storage + assert '"model_artifacts_bucket_public_access_block"' in infrastructure_storage From aea25fc7ba5988f4cf907ccd43bcc56bd28007fd Mon Sep 17 00:00:00 2001 From: John Forstmeier Date: Fri, 13 Mar 2026 21:35:55 -0400 Subject: [PATCH 5/6] Address PR #792 feedback: fix service URL, ECR names, and add non-root container users - Fix default DATAMANAGER_BASE_URL fallback in ensemble_manager/server.py from "http://datamanager:8080" to "http://data-manager:8080" to match the docker-compose service name after the renaming in this PR - Fix ECR repository names in infrastructure/storage.py to use underscore-prefixed application names (fund/data_manager-server, fund/portfolio_manager-server, fund/ensemble_manager-server) to match the CI matrix application_name values used in the mask push command (fund/${application_name}-${stage_name}) - Fix ensemble-manager docker-compose service to reference AWS_ECR_ENSEMBLE_MANAGER_SERVER_IMAGE_ARN instead of the stale AWS_ECR_EQUITY_PRICE_MODEL_SERVER_IMAGE_ARN - Add non-root appuser (uid 10001) to applications/ensemble_manager/Dockerfile server stage - Add non-root appuser (uid 10001) to models/tide/Dockerfile trainer stage Co-Authored-By: Claude Sonnet 4.6 --- applications/ensemble_manager/Dockerfile | 5 +++++ .../ensemble_manager/src/ensemble_manager/server.py | 4 +++- infrastructure/docker-compose.yaml | 2 +- infrastructure/storage.py | 6 +++--- models/tide/Dockerfile | 5 +++++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/applications/ensemble_manager/Dockerfile b/applications/ensemble_manager/Dockerfile index 1ade78b24..6c4ba4aa3 100644 --- a/applications/ensemble_manager/Dockerfile +++ b/applications/ensemble_manager/Dockerfile @@ -28,6 +28,11 @@ COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /bin/uv COPY --from=builder /app /app +RUN useradd --system --uid 10001 --create-home appuser && \ + chown -R appuser:appuser /app + +USER appuser + EXPOSE 8080 ENTRYPOINT ["uv", "run", "--package", "ensemble_manager", "uvicorn", "ensemble_manager.server:application", "--host", "0.0.0.0", "--port", "8080", "--app-dir", "applications/ensemble_manager/src"] diff --git a/applications/ensemble_manager/src/ensemble_manager/server.py b/applications/ensemble_manager/src/ensemble_manager/server.py index ee43dda31..d908135a0 100644 --- a/applications/ensemble_manager/src/ensemble_manager/server.py +++ b/applications/ensemble_manager/src/ensemble_manager/server.py @@ -59,7 +59,9 @@ logger = structlog.get_logger() -DATAMANAGER_BASE_URL = os.getenv("FUND_DATAMANAGER_BASE_URL", "http://datamanager:8080") +DATAMANAGER_BASE_URL = os.getenv( + "FUND_DATAMANAGER_BASE_URL", "http://data-manager:8080" +) MODEL_VERSION_SSM_PARAMETER = "/fund/ensemble_manager/model_version" diff --git a/infrastructure/docker-compose.yaml b/infrastructure/docker-compose.yaml index bf11175f1..82f7d503e 100644 --- a/infrastructure/docker-compose.yaml +++ b/infrastructure/docker-compose.yaml @@ -38,7 +38,7 @@ services: timeout: 5s retries: 3 ensemble-manager: - image: ${AWS_ECR_EQUITY_PRICE_MODEL_SERVER_IMAGE_ARN} + image: ${AWS_ECR_ENSEMBLE_MANAGER_SERVER_IMAGE_ARN} ports: - 8082:8080 environment: diff --git a/infrastructure/storage.py b/infrastructure/storage.py index 952f5cbab..ff52c8b84 100644 --- a/infrastructure/storage.py +++ b/infrastructure/storage.py @@ -96,7 +96,7 @@ # retain_on_delete=True and add pulumi import statements to the maskfile up command. data_manager_repository = aws.ecr.Repository( "data_manager_repository", - name="fund/data-manager-server", + name="fund/data_manager-server", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -107,7 +107,7 @@ portfolio_manager_repository = aws.ecr.Repository( "portfolio_manager_repository", - name="fund/portfolio-manager-server", + name="fund/portfolio_manager-server", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( @@ -118,7 +118,7 @@ ensemble_manager_repository = aws.ecr.Repository( "ensemble_manager_repository", - name="fund/ensemble-manager-server", + name="fund/ensemble_manager-server", image_tag_mutability="MUTABLE", force_delete=True, image_scanning_configuration=aws.ecr.RepositoryImageScanningConfigurationArgs( diff --git a/models/tide/Dockerfile b/models/tide/Dockerfile index 950e6670c..582150f51 100644 --- a/models/tide/Dockerfile +++ b/models/tide/Dockerfile @@ -40,4 +40,9 @@ COPY --from=builder /app /app ENV PYTHONPATH=/app/models/tide/src +RUN useradd --system --uid 10001 --create-home appuser && \ + chown -R appuser:appuser /app + +USER appuser + ENTRYPOINT ["uv", "run", "--package", "tide", "python", "-m", "tide.workflow"] From 98e2e51b7c72889a298019caddab72c725d9cfe4 Mon Sep 17 00:00:00 2001 From: John Forstmeier Date: Fri, 13 Mar 2026 23:27:37 -0400 Subject: [PATCH 6/6] Add bot pull request feedback --- CLAUDE.md | 1 + .../src/ensemble_manager/server.py | 3 +- infrastructure/Pulumi.production.yaml | 6 +- infrastructure/iam.py | 5 +- infrastructure/parameters.py | 5 +- infrastructure/storage.py | 56 +++++++++++++++++++ .../tests/test_infrastructure_storage.py | 11 ++++ maskfile.md | 2 +- 8 files changed, 80 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5772954fa..e0f7dae76 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,6 +48,7 @@ This is a collection of guidelines and references. - `applications/` folder contains deployable services and training workflows - `libraries/` folder contains shared code resources - `infrastructure/` folder contains Pulumi infrastructure as code +- `models/` folder contains model definitions and training code - See `README.md` "Principles" section for developer philosophy - If something goes wrong during a task, stop immediately and re-plan rather than continuing - Use subagents to keep main context window clean and offload research, exploration, and analysis work diff --git a/applications/ensemble_manager/src/ensemble_manager/server.py b/applications/ensemble_manager/src/ensemble_manager/server.py index d908135a0..a263f39a4 100644 --- a/applications/ensemble_manager/src/ensemble_manager/server.py +++ b/applications/ensemble_manager/src/ensemble_manager/server.py @@ -62,7 +62,8 @@ DATAMANAGER_BASE_URL = os.getenv( "FUND_DATAMANAGER_BASE_URL", "http://data-manager:8080" ) -MODEL_VERSION_SSM_PARAMETER = "/fund/ensemble_manager/model_version" +_environment = os.environ.get("FUND_ENVIRONMENT", "development") +MODEL_VERSION_SSM_PARAMETER = f"/fund/{_environment}/ensemble-manager/model-version" def find_latest_artifact_key( diff --git a/infrastructure/Pulumi.production.yaml b/infrastructure/Pulumi.production.yaml index d28f9eaec..36a400720 100644 --- a/infrastructure/Pulumi.production.yaml +++ b/infrastructure/Pulumi.production.yaml @@ -12,15 +12,15 @@ config: - launch_infrastructure.yaml - teardown_infrastructure.yaml fund:datamanagerSecretName: - secure: AAABAETFtwN9i15kl2zVie22BRp5EkF3PBV0xwgNfu61tGsy0z4d9ZXa6hMZ6d2kBAssfYksqgPVZLA= + secure: AAABAH+qx0nGwnPg8Y3tU/ZHFqQggEn1WgFzLIS8KpidlGYX7oZ1Ly0rP1cQReaFhYqrYWKwn2OL2E6we92WQw== fund:datamanagerSecretValue: secure: AAABAChRujETi2wJ+Tw2jj64uVvght6AMW5E+nuePkXqKXWyrMSVEIVQ5o9NRIHRRQ0wh1F3WzRW+BysSZhpdH/fULYOz9gtMmKlcG01uAApHdy8cWBk fund:portfoliomanagerSecretName: - secure: AAABAKf+lbs+gJkBwQNt2mFUMhX/RnrgWwEFEaLwND4l8yeCTIrUa/guHH0bl22SllDrOm2yu/oYnKBa77AhVA== + secure: AAABAO85LMKlG40+NR/4KkfLOt7lj0MO6VaPCuLKEkonxNT2qBgQxmOODHnqRWfwjOz23F422ykryZqOwAvaiELoo0oy fund:portfoliomanagerSecretValue: secure: AAABAKW6YZV5HEHJBO04eh9QW/woEl/cvAgoDiURRH5e+YUwr+Muw6IR4hny+NQXrftQhk0EGS/E6OA1n9nElWqvjycm23MV/xR4CTlvW60CRQoQrOSWZuDdqmwv4KUa7qxVEZqYrPO86qJDBfXDIDJ5lkW8R1XmDR/+ubIV3nrkgvkEnXEtZWx1hzeXSsgfR7759Q0QXbSs8AfhGT8hxztiDxwDsSFQOA== fund:sharedSecretName: - secure: AAABAN+AOhwp6jr/FKgLoyOBHVEeiG8kxzGJrsTr30gnj5JiuQs+YJmmFRSd0cdOHJotfJ8Y + secure: AAABAGou762nEut6P9rKIDkTT/r45oL5HNej4vGZw49nRGWcRoyBS3xUXcX3UpaS/oRU3HouIuGiDg== fund:sharedSecretValue: secure: AAABACKTBgsKXMGiDo/WXf5/WTwxHIKAKYUGOMhCecEe09+g/huViXxO1fYA+I2EdIcxBk8zerAoxkOGNUMkik+45skuj3vUYHraOLiKmzSt9h7Z1R56ixPoNMrTSbCMjpHOHZSji0G7lH2qCdCj6jGH/aouZjZRsnPLGa5/pxjhe+1aUtuvwLoqr6IlyuPEkw== fund:randomSuffix: diff --git a/infrastructure/iam.py b/infrastructure/iam.py index 199f1afd3..79524dedb 100644 --- a/infrastructure/iam.py +++ b/infrastructure/iam.py @@ -23,6 +23,7 @@ github_repository, github_workflow_refs, region, + stack_name, tags, training_notification_recipient_emails, training_notification_sender_email, @@ -533,7 +534,7 @@ training_notification_sender_email_parameter = aws.ssm.Parameter( "training_notification_sender_email_parameter", - name="/fund/training/training_notification_sender_email", + name=f"/fund/{stack_name}/training/notification-sender-email", type="SecureString", value=training_notification_sender_email, tags=tags, @@ -541,7 +542,7 @@ training_notification_recipients_parameter = aws.ssm.Parameter( "training_notification_recipients_parameter", - name="/fund/training/training_notification_recipients", + name=f"/fund/{stack_name}/training/notification-recipients", type="SecureString", value=training_notification_recipient_emails, tags=tags, diff --git a/infrastructure/parameters.py b/infrastructure/parameters.py index ae65aa1f8..e33f3c58d 100644 --- a/infrastructure/parameters.py +++ b/infrastructure/parameters.py @@ -6,6 +6,7 @@ import pulumi import pulumi_aws as aws +from config import stack_name tags = { "project": "fund", @@ -16,7 +17,7 @@ # Portfolio Manager Configuration uncertainty_threshold = aws.ssm.Parameter( "ssm_uncertainty_threshold", - name="/fund/portfolio-manager/uncertainty_threshold", + name=f"/fund/{stack_name}/portfolio-manager/uncertainty-threshold", type="String", value="1.5", description="Maximum inter-quartile range for predictions to be considered valid", @@ -26,7 +27,7 @@ # Ensemble Manager Configuration ensemble_manager_model_version = aws.ssm.Parameter( "ssm_ensemble_manager_model_version", - name="/fund/ensemble-manager/model_version", + name=f"/fund/{stack_name}/ensemble-manager/model-version", type="String", value="latest", description=( diff --git a/infrastructure/storage.py b/infrastructure/storage.py index ff52c8b84..d56f57018 100644 --- a/infrastructure/storage.py +++ b/infrastructure/storage.py @@ -1,7 +1,27 @@ +import json + import pulumi import pulumi_aws as aws from config import random_suffix, tags +_ecr_lifecycle_policy = json.dumps( + { + "rules": [ + { + "rulePriority": 1, + "description": "Expire untagged images immediately", + "selection": { + "tagStatus": "untagged", + "countType": "sinceImagePushed", + "countUnit": "days", + "countNumber": 0, + }, + "action": {"type": "expire"}, + } + ] + } +) + # S3 Data Bucket for storing equity bars, predictions, portfolios # alias: migrated from aws:s3/bucket:Bucket to aws:s3/bucketV2:BucketV2 data_bucket = aws.s3.BucketV2( @@ -105,6 +125,12 @@ tags=tags, ) +aws.ecr.LifecyclePolicy( + "data_manager_repository_lifecycle", + repository=data_manager_repository.name, + policy=_ecr_lifecycle_policy, +) + portfolio_manager_repository = aws.ecr.Repository( "portfolio_manager_repository", name="fund/portfolio_manager-server", @@ -116,6 +142,12 @@ tags=tags, ) +aws.ecr.LifecyclePolicy( + "portfolio_manager_repository_lifecycle", + repository=portfolio_manager_repository.name, + policy=_ecr_lifecycle_policy, +) + ensemble_manager_repository = aws.ecr.Repository( "ensemble_manager_repository", name="fund/ensemble_manager-server", @@ -127,6 +159,12 @@ tags=tags, ) +aws.ecr.LifecyclePolicy( + "ensemble_manager_repository_lifecycle", + repository=ensemble_manager_repository.name, + policy=_ecr_lifecycle_policy, +) + tide_trainer_repository = aws.ecr.Repository( "tide_trainer_repository", name="fund/tide-trainer", @@ -138,6 +176,12 @@ tags=tags, ) +aws.ecr.LifecyclePolicy( + "tide_trainer_repository_lifecycle", + repository=tide_trainer_repository.name, + policy=_ecr_lifecycle_policy, +) + training_server_repository = aws.ecr.Repository( "training_server_repository", name="fund/training-server", @@ -149,6 +193,12 @@ tags=tags, ) +aws.ecr.LifecyclePolicy( + "training_server_repository_lifecycle", + repository=training_server_repository.name, + policy=_ecr_lifecycle_policy, +) + training_worker_repository = aws.ecr.Repository( "training_worker_repository", name="fund/training-worker", @@ -160,6 +210,12 @@ tags=tags, ) +aws.ecr.LifecyclePolicy( + "training_worker_repository_lifecycle", + repository=training_worker_repository.name, + policy=_ecr_lifecycle_policy, +) + # Generate image URIs - these will be used in task definitions # For initial deployment, use a placeholder that will be updated when images are pushed data_manager_image_uri = data_manager_repository.repository_url.apply( diff --git a/libraries/python/tests/test_infrastructure_storage.py b/libraries/python/tests/test_infrastructure_storage.py index 264cfb85b..82de9d241 100644 --- a/libraries/python/tests/test_infrastructure_storage.py +++ b/libraries/python/tests/test_infrastructure_storage.py @@ -20,3 +20,14 @@ def test_storage_contains_s3_public_access_block_resources() -> None: assert '"data_bucket_public_access_block"' in infrastructure_storage assert '"model_artifacts_bucket_public_access_block"' in infrastructure_storage + + +def test_storage_contains_ecr_lifecycle_policy_resources() -> None: + infrastructure_storage = load_infrastructure_storage() + + assert '"data_manager_repository_lifecycle"' in infrastructure_storage + assert '"portfolio_manager_repository_lifecycle"' in infrastructure_storage + assert '"ensemble_manager_repository_lifecycle"' in infrastructure_storage + assert '"tide_trainer_repository_lifecycle"' in infrastructure_storage + assert '"training_server_repository_lifecycle"' in infrastructure_storage + assert '"training_worker_repository_lifecycle"' in infrastructure_storage diff --git a/maskfile.md b/maskfile.md index 741766436..175bd84ca 100644 --- a/maskfile.md +++ b/maskfile.md @@ -223,7 +223,7 @@ pulumi import --yes --generate-code=false aws:s3/bucketServerSideEncryptionConfi pulumi import --yes --generate-code=false aws:s3/bucketPublicAccessBlock:BucketPublicAccessBlock model_artifacts_bucket_public_access_block "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true pulumi import --yes --generate-code=false aws:s3/bucketVersioning:BucketVersioning model_artifacts_bucket_versioning "fund-model-artifacts-${RANDOM_SUFFIX}" 2>/dev/null || true -pulumi import --yes --generate-code=false aws:ssm/parameter:Parameter ssm_ensemble_manager_model_version "/fund/ensemble-manager/model_version" 2>/dev/null || true +pulumi import --yes --generate-code=false aws:ssm/parameter:Parameter ssm_ensemble_manager_model_version "/fund/production/ensemble-manager/model-version" 2>/dev/null || true echo "Importing resources complete"