diff --git a/README.md b/README.md index 9f88a1b..2005eea 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# **FastSim – Event-Loop Aware Simulation for Backend Systems** +# **AsyncFlow – Event-Loop Aware Simulation for Backend Systems** ## **1. Overview** Modern asynchronous Python stacks such as **FastAPI + Uvicorn** deliver impressive performance, yet capacity planning for production workloads often relies on guesswork, costly cloud-based load tests, or late-stage troubleshooting. -**FastSim** addresses this challenge by providing a **digital twin** of your service that can be run entirely offline. It models event-loop behaviour, resource constraints, and request lifecycles, enabling you to forecast performance under different workloads and architectural choices **before deployment**. +**AsyncFlow** addresses this challenge by providing a **digital twin** of your service that can be run entirely offline. It models event-loop behaviour, resource constraints, and request lifecycles, enabling you to forecast performance under different workloads and architectural choices **before deployment**. -FastSim allows you to answer questions such as: +AsyncFlow allows you to answer questions such as: * *What happens to p95 latency if traffic doubles during a peak event?* * *How many cores are required to maintain SLAs at scale?* @@ -37,7 +37,7 @@ Until published, clone the repository and install in editable mode: - Python 3.11+ (recommended 3.12) - Poetry ≥ 1.6 -FastSim uses [Poetry](https://python-poetry.org/) for dependency management. +AsyncFlow uses [Poetry](https://python-poetry.org/) for dependency management. If you do not have Poetry installed, follow these steps. ### 3.1 Install Poetry (official method) @@ -64,8 +64,8 @@ curl -sSL https://install.python-poetry.org | python3 - ```bash # Clone the repository -git clone https://github.com/GioeleB00/Fastsim-Backend.git -cd Fastsim-Backend +git clone https://github.com/GioeleB00/AsyncFlow-Backend.git +cd AsyncFlow-Backend # Configure Poetry to always create a local `.venv` inside the project poetry config virtualenvs.in-project true @@ -171,9 +171,9 @@ from pathlib import Path import simpy import matplotlib.pyplot as plt -from app.config.constants import LatencyKey -from app.runtime.simulation_runner import SimulationRunner -from app.metrics.analyzer import ResultsAnalyzer +from asyncflow.config.constants import LatencyKey +from asyncflow.runtime.simulation_runner import SimulationRunner +from asyncflow.metrics.analyzer import ResultsAnalyzer def print_latency_stats(res: ResultsAnalyzer) -> None: """Print latency statistics returned by the analyzer.""" @@ -229,7 +229,7 @@ you will see the latency stats ## **5. Target Users and Use Cases** -| Audience | Challenge | FastSim Value | +| Audience | Challenge | AsyncFlow Value | | ------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------- | | Backend Engineers | Sizing services for variable workloads | Model endpoint workflows and resource bottlenecks before deployment | | DevOps / SRE | Balancing cost and SLA | Simulate scaling scenarios to choose optimal capacity | @@ -244,7 +244,7 @@ you will see the latency stats The project follows a standard Python package layout, managed with Poetry. ``` -Fastsim-Backend/ +AsyncFlow-Backend/ ├── examples/ # Examples payloads and datasets ├── scripts/ # Utility scripts (linting, startup) ├── docs/ # Project vision and technical documentation @@ -265,7 +265,7 @@ Fastsim-Backend/ ## **7. Development Workflow** -FastSim uses **Poetry** for dependency management and enforces quality via **Ruff** and **MyPy**. +AsyncFlow uses **Poetry** for dependency management and enforces quality via **Ruff** and **MyPy**. | Task | Command | Description | | ------------- | --------------------------------- | -------------------------------------- | diff --git a/docs/dev_workflow_guide.md b/docs/dev_workflow_guide.md index c0a9705..8c1974c 100644 --- a/docs/dev_workflow_guide.md +++ b/docs/dev_workflow_guide.md @@ -1,31 +1,23 @@ # **Development Workflow & Architecture Guide** -This document outlines the standardized development workflow, repository architecture, and branching strategy for the backend of the FastSim project. Adhering to these guidelines ensures consistency, maintainability, and a scalable development process. +This document outlines the standardized development workflow, repository architecture, and branching strategy for the backend of the AsyncFlow project. Adhering to these guidelines ensures consistency, maintainability, and a scalable development process. -## 1. Technology Stack -The project is built upon the following core technologies: +## 1. Repository Layout -- **Backend**: FastAPI -- **Backend Package Manager**: Poetry -- **Frontend**: React + JavaScript -- **Database**: PostgreSQL -- **Caching**: Redis -- **Containerization**: Docker +### 1.1 Backend Service (`AsyncFlow-backend`) -### 2.1 Backend Service (`FastSim-backend`) - -The repository hosts the entire FastAPI backend for FastSim. +The repository hosts the entire FastAPI backend for AsyncFlow. Its job is to expose the REST API, run the discrete-event simulation, talk to the database, and provide metrics. ``` -fastsim-backend/ +AsyncFlow-backend/ ├── example/ # examples of working simulations │ ├── data ├── scripts/ # helper bash scripts (lint, dev-startup, …) │ └── quality-check.sh ├── docs/ # project vision & low-level docs -│ └── fastsim-documentation/ +│ └── AsyncFlow-documentation/ ├── tests/ # unit & integration tests │ ├── unit/ │ └── integration/ @@ -37,15 +29,18 @@ fastsim-backend/ │ ├── runtime/ # simulation core │ │ ├── rqs_state.py # RequestState & Hop │ │ ├── simulation_runner.py # logic to initialize the whole simulation - | └── actors/ # SimPy “actors”: Edge, Server, Client, RqsGenerator - ├── pybuilder/ # Pythonic way to build the simulation payload +│ │ └── actors/ # SimPy “actors”: Edge, Server, Client, RqsGenerator +│ ├── pybuilder/ # Pythonic way to build the simulation payload │ ├── samplers/ # stochastic samplers (Gaussian-Poisson, etc.) │ ├── schemas/ # Pydantic input/output models ├── poetry.lock ├── pyproject.toml └── README.md ``` -### **What each top-level directory in `src/app` does** + +> Note: If your package name under `src/` is `asyncflow/` (instead of `app/`), the structure is identical—only the package folder name changes. + +### What each top-level directory in `src/app` does | Directory | Purpose | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -56,31 +51,32 @@ fastsim-backend/ | | - **`rqs_state.py`** — Defines `RequestState` and `Hop` for tracking request lifecycle. | | | - **`simulation_runner.py`** — Entry point for initialising and running simulations. | | | - **`actors/`** — SimPy actor classes representing system components (`RqsGenerator`, `Client`, `Server`, `Edge`) and their behaviour. | +| **`pybuilder/`** | Pythonic builder to programmatically construct validated simulation payloads (alternative to YAML). | | **`samplers/`** | Random-variable samplers for stochastic simulation. Supports Poisson, Normal, and mixed distributions for modelling inter-arrival times and service steps. | | **`schemas/`** | Pydantic models for input/output validation and serialisation. Includes scenario definitions, topology graphs, simulation settings, and results payloads. | --- -### **Other Top-Level Directories** +### Other Top-Level Directories | Directory | Purpose | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **`example/`** | Ready-to-run simulation scenarios and example configurations. Includes `data/` with YAML definitions and scripts to demonstrate engine usage. | | **`scripts/`** | Utility shell scripts for development workflow, linting, formatting, and local startup (`quality-check.sh`, etc.). | -| **`docs/`** | Project documentation. Contains both high-level vision documents and low-level technical references (`fastsim-documentation/`). | +| **`docs/`** | Project documentation. Contains both high-level vision documents and low-level technical references (`AsyncFlow-documentation/`). | | **`tests/`** | Automated test suite, split into **unit** and **integration** tests to verify correctness of both individual components and end-to-end scenarios. | --- -## 3. Branching Strategy: Git Flow +## 2. Branching Strategy: Git Flow -To manage code development and releases in a structured manner, we use the **Git Flow** branching model. +To manage code development and releases in a structured manner, we use the **Git Flow** branching model—with an additional **refactor** branch type for non-feature refactoring work. ### Git Flow Workflow Diagram ```mermaid --- -title: Git Flow +title: Git Flow (with refactor/*) --- gitGraph @@ -89,6 +85,7 @@ gitGraph checkout develop commit id: "Setup Project" + %% feature branch branch feature/user-authentication checkout feature/user-authentication commit id: "feat: Add login form" @@ -96,6 +93,15 @@ gitGraph checkout develop merge feature/user-authentication + %% refactor branch (no new features, code cleanup/improvements) + branch refactor/performance-cleanup + checkout refactor/performance-cleanup + commit id: "refactor: simplify hot path" + commit id: "refactor: remove dead code" + checkout develop + merge refactor/performance-cleanup + + %% release branch branch release/v1.0.0 checkout release/v1.0.0 commit id: "fix: Pre-release bug fixes" tag: "v1.0.0" @@ -104,6 +110,7 @@ gitGraph checkout develop merge release/v1.0.0 + %% hotfix branch checkout main branch hotfix/critical-login-bug checkout hotfix/critical-login-bug @@ -122,70 +129,128 @@ This workflow is built upon two long-lived branches and several temporary, suppo #### Main Branches -1. **`main`** - * **Purpose**: This branch contains **production-ready, stable code**. Every commit on `main` represents a new, official release. - * **Rules**: You should **never** commit directly to `main`. It only receives merges from `release/*` and `hotfix/*` branches. Each merge should be tagged with a version number (e.g., `v1.0.0`). +1. **`main`** + **Purpose**: Production-ready, stable code. Every commit on `main` represents an official release. + **Rules**: Never commit directly to `main`. It only receives merges from `release/*` and `hotfix/*`. Each merge should be **tagged** (e.g., `v1.0.0`). -2. **`develop`** - * **Purpose**: This is the main **integration branch** for ongoing development. It contains all completed and tested features that are planned for the next release. - * **Rules**: It's the base for creating new `feature/*` branches. It reflects the most up-to-date state of development. +1. **`develop`** + **Purpose**: The main integration branch for ongoing development. It contains all completed and tested changes planned for the next release. + **Rules**: Base for `feature/*` and `refactor/*` branches. Reflects the most up-to-date development state. #### Supporting Branches -3. **`feature/*`** (e.g., `feature/user-authentication`) - * **Purpose**: To develop a new, specific feature in isolation. - * **Lifecycle**: - 1. Branched off of **`develop`**. - 2. Once development is complete, a **Pull Request (PR)** is opened to merge the changes back into **`develop`**. - 3. After the merge, the `feature/*` branch can be deleted. +3. **`feature/*`** (e.g., `feature/user-authentication`) + **Purpose**: Develop a new, specific feature in isolation. + **Lifecycle**: + + 1. Branched off **`develop`**. + 1. When complete, open a **Pull Request (PR)** back into **`develop`**. + 3. Delete the branch after merge. + +3. **`refactor/*`** (e.g., `refactor/performance-cleanup`) **← new** + **Purpose**: Perform **non-functional code changes** (no new features), such as internal restructurings, performance optimisations, reducing technical debt, renaming, file moves, or dependency hygiene. + **Rules**: -4. **`release/*`** (e.g., `release/v1.2.0`) - * **Purpose**: To prepare for a new production release. This branch is used for final bug fixes, documentation updates, and last-minute testing. It freezes the feature set for the release. - * **Lifecycle**: - 1. Branched off of **`develop`** when it's decided that the next version is feature-complete. - 2. When ready, it is merged into **`main`** (and tagged) and also back into **`develop`** to ensure any fixes made during the release phase are not lost. - 3. After merging, the `release/*` branch can be deleted. + * Must **not** introduce user-visible features or breaking API/DB changes. + * Prefer commit prefix `refactor:`; avoid `feat:`. + * Keep changes scoped and well-described to simplify review. + **Lifecycle**: -5. **`hotfix/*`** (e.g., `hotfix/critical-login-bug`) - * **Purpose**: To quickly patch a critical bug discovered in the production version (`main`). - * **Lifecycle**: - 1. Branched off of **`main`** (from the specific version tag). - 2. Once the fix is ready, it is merged into **`main`** (and a new patch version tag is created, e.g., `v1.0.1`). - 3. It is also merged into **`develop`** to ensure the fix is included in future releases. - 4. After merging, the `hotfix/*` branch can be deleted. + 1. Branched off **`develop`**. + 1. Open a **PR** back into **`develop`** (same review gates as features). + 3. Delete the branch after merge. -## 4. Continuous Integration / Continuous Delivery (CI/CD) Pipeline +5. **`release/*`** (e.g., `release/v1.1.0`) + **Purpose**: Prepare a production release—final bug fixes, docs, and last-minute tests. The feature set is frozen here. + **Lifecycle**: + + 1. Branched off **`develop`** when feature-complete. + 1. Merge into **`main`** (tag version) and back into **`develop`**. + 3. Delete after merges. + +6. **`hotfix/*`** (e.g., `hotfix/critical-login-bug`) + **Purpose**: Quickly patch a critical bug in production. + **Lifecycle**: + + 1. Branched off **`main`** (from a specific tag). + 1. Merge into **`main`** (tag a patch version, e.g., `v1.0.1`) **and** into **`develop`**. + 3. Delete after merges. + +**When to choose which branch?** + +* **New behavior / endpoints / DB migrations** → `feature/*` +* **Internal code improvements only** → `refactor/*` +* **Release prep** → `release/*` +* **Production emergency** → `hotfix/*` + +--- -A robust CI/CD pipeline guarantees that every change pushed to the repository is automatically validated, packaged, and—when appropriate—promoted to the next environment. -Our pipeline is built with **GitHub Actions** and follows a layered approach that mirrors the Git Flow branching model. +## 3. Continuous Integration / Continuous Delivery (CI/CD) Pipeline -We will start to describe the CI part related to push and PR in the develop branch of the backend service. +A robust CI/CD pipeline guarantees that every change is automatically validated, packaged, and—when appropriate—promoted to the next environment. Our pipeline is built with **GitHub Actions** and mirrors the branching model. -### 4.1 CI for project-backend on `develop` +We start with the CI part related to pushes and PRs in the backend service. -#### 4.1.1 Goals +### 3.1 CI for project-backend on `develop` -* **Fast feedback** – linting, type-checking, and unit tests finish in under a minute for every Pull Request. +#### 3.1.1 Goals + +* **Fast feedback** – linting, type-checking, and unit tests finish quickly for every Pull Request. * **Confidence in integration** – migrations, integration tests, and Docker smoke-tests run on every push to `develop`. -* **Deployment safety** – only artifacts produced from a green pipeline can be released or deployed. -* **Parity with production** – the same multi-stage Dockerfile is built and probed in CI, preventing “works-on-my-machine” surprises. +* **Deployment safety** – only artifacts from a green pipeline can be released/deployed. +* **Parity with production** – the same multi-stage Dockerfile is built and probed in CI. -### 4.1.2 Pipeline Layers +#### 3.1.1 Pipeline Layers * **Quick Suite (PR to `develop`)** *Runs in seconds; no external services or containers.* - * Black, isort, Flake8 + * Black, isort, Flake8 (or Ruff if adopted) * mypy static type-checking * Unit tests only (`pytest -m "not integration"`) * **Full Suite (push to `develop`)** *Runs in a few minutes; includes real services and Docker.* - + * Full test suite, including `@pytest.mark.integration` tests - + * Database migrations (PostgreSQL) against a disposable instance + * Redis available for tests if required + * Build multi-stage Docker image and run a quick smoke test + +### 3.1 CI for `feature/*` and `refactor/*` + +* **On PR to `develop`**: run the **Quick Suite** (lint, type-checking, unit tests). +* **Optional (recommended for large changes)**: allow a manual or scheduled **Full Suite** run for the branch to catch integration issues early. +* **On merge to `develop`**: the **Full Suite** runs (as described above). + +> `refactor/*` branches should maintain **zero behavior change**. If a refactor has the potential to alter behavior (e.g., performance-sensitive code), add targeted tests and consider a manual Full Suite run before merge. + +### 3.3 CI for `release/*` + +* Always run the **Full Suite**. +* Build and publish versioned artifacts/images to the registry with the release tag. +* Prepare release notes and changelog generation. +### 3.3 CI for `hotfix/*` +* Run the **Full Suite** against the hotfix branch. +* Tag the patch release on merge to `main` and propagate the merge back to `develop`. +--- + +## 4. Quality Gates & Conventions + +* **Static Analysis**: mypy (no new type errors). +* **Style**: Black/Flake8/isort or Ruff; no lint violations. +* **Tests**: + + * Unit tests for new logic or refactor touch points. + * Integration tests for cross-layer behavior. +* **Commits**: Conventional commits (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:` …). +* **Code Review**: PRs must be reviewed and approved; refactors must include rationale in the PR description (what changed, why safe). +* **Documentation**: Update `README`, `docs/`, and API docs when applicable. + +--- +By following this workflow—now with the **refactor** branch type—you keep feature development cleanly separated from codebase improvements, reduce merge friction, and maintain a predictable, high-quality delivery pipeline. diff --git a/docs/fastsim-docs/metrics_to_measure.md b/docs/fastsim-docs/metrics_to_measure.md index 390f6d9..bcffd33 100644 --- a/docs/fastsim-docs/metrics_to_measure.md +++ b/docs/fastsim-docs/metrics_to_measure.md @@ -1,8 +1,8 @@ -### **FastSim — Simulation Metrics** +### **AsyncFlow — Simulation Metrics** -Metrics are the lifeblood of any simulation, transforming a series of abstract events into concrete, actionable insights about system performance, resource utilization, and potential bottlenecks. FastSim provides a flexible and robust metrics collection system designed to give you a multi-faceted view of your system's behavior under load. +Metrics are the lifeblood of any simulation, transforming a series of abstract events into concrete, actionable insights about system performance, resource utilization, and potential bottlenecks. AsyncFlow provides a flexible and robust metrics collection system designed to give you a multi-faceted view of your system's behavior under load. -To achieve this, FastSim categorizes metrics into three distinct types: +To achieve this, AsyncFlow categorizes metrics into three distinct types: 1. **Sampled Metrics (`SampledMetricName`):** These metrics provide a **time-series view** of the system's state. They are captured at fixed, regular intervals (e.g., every 5 milliseconds). This methodology is ideal for understanding trends and measuring the continuous utilization of finite resources. Think of them as periodic snapshots of your system's health. diff --git a/docs/fastsim-docs/requests_generator.md b/docs/fastsim-docs/requests_generator.md index a15e241..eab8080 100644 --- a/docs/fastsim-docs/requests_generator.md +++ b/docs/fastsim-docs/requests_generator.md @@ -6,7 +6,7 @@ This document describes the design of the **requests generator**, which models a ## Model Inputs and Output -Following the FastSim philosophy, we accept a small set of input parameters to drive a “what-if” analysis in a pre-production environment. These inputs let you explore reliability and cost implications under different traffic scenarios. +Following the AsyncFlow philosophy, we accept a small set of input parameters to drive a “what-if” analysis in a pre-production environment. These inputs let you explore reliability and cost implications under different traffic scenarios. ## **Inputs** @@ -386,4 +386,4 @@ The sampling window length governs how often we re-sample $U$. It should reflect **Key takeaway:** By structuring the generator as -$\Lambda = U\,\lambda_r/60$ with a two-stage Poisson→Exponential sampler, FastSim efficiently reproduces compound Poisson traffic dynamics without any complex CDF inversion. +$\Lambda = U\,\lambda_r/60$ with a two-stage Poisson→Exponential sampler, AsyncFlow efficiently reproduces compound Poisson traffic dynamics without any complex CDF inversion. diff --git a/docs/fastsim-docs/runtime_and_resources.md b/docs/fastsim-docs/runtime_and_resources.md index a5dca9c..32f1611 100644 --- a/docs/fastsim-docs/runtime_and_resources.md +++ b/docs/fastsim-docs/runtime_and_resources.md @@ -1,10 +1,10 @@ Of course. This is an excellent request. A deep dive into the "why" and the real-world analogies is what makes documentation truly valuable. -Here is the comprehensive, detailed documentation for the FastSim Runtime Layer, written in English, incorporating all your requests. +Here is the comprehensive, detailed documentation for the AsyncFlow Runtime Layer, written in English, incorporating all your requests. ----- -# **FastSim — The Runtime Layer Documentation** +# **AsyncFlow — The Runtime Layer Documentation** *(Version July 2025 – Aligned with `app/runtime` and `app/resources`)* @@ -80,7 +80,7 @@ Think of `RequestState` as a request context in a modern microservices architect ## **4 The Resource Layer — Modelling Contention ⚙️** In real infrastructures every machine has a hard ceiling: only *N* CPU cores, only *M* MB of RAM. -FastSim mirrors that physical constraint through the **Resource layer**, which exposes pre-filled SimPy containers that actors must draw from. If a token is not available the coroutine simply blocks — giving you back-pressure “for free”. +AsyncFlow mirrors that physical constraint through the **Resource layer**, which exposes pre-filled SimPy containers that actors must draw from. If a token is not available the coroutine simply blocks — giving you back-pressure “for free”. --- @@ -127,7 +127,7 @@ defensive checks beyond the simple dictionary lookup. | RAM container tokens | **cgroup memory limit** or a pod’s allocatable memory; once exhausted new workloads must wait. | Just like a Kubernetes scheduler won’t place a pod if a node lacks free CPU/RAM, -FastSim won’t let an actor proceed until it obtains the necessary tokens. +AsyncFlow won’t let an actor proceed until it obtains the necessary tokens. ## **5. The Actors: Bringing the System to Life** @@ -448,4 +448,4 @@ def _forwarder(self) -> Generator[simpy.Event, None, None]: With these mechanics the `LoadBalancerRuntime` faithfully emulates behaviour of production LBs (NGINX, HAProxy, AWS ALB) while remaining lightweight and -fully deterministic inside the FastSim event loop. +fully deterministic inside the AsyncFlow event loop. diff --git a/docs/fastsim-docs/simulation_input.md b/docs/fastsim-docs/simulation_input.md index ad89247..d5d860c 100644 --- a/docs/fastsim-docs/simulation_input.md +++ b/docs/fastsim-docs/simulation_input.md @@ -1,8 +1,8 @@ -# FastSim — Simulation Input Schema (v2) +# AsyncFlow — Simulation Input Schema (v2) -This document describes the **complete input contract** used by FastSim to run a simulation, the **design rationale** behind it, and the **guarantees** provided by the validation layer. It closes with an **end-to-end example** (YAML) you can drop into the project and run as-is. +This document describes the **complete input contract** used by AsyncFlow to run a simulation, the **design rationale** behind it, and the **validation guarantees** enforced by the Pydantic layer. At the end you’ll find an **end-to-end YAML example** you can run as-is. -The entry point is the Pydantic model: +The entry point is: ```python class SimulationPayload(BaseModel): @@ -14,40 +14,38 @@ class SimulationPayload(BaseModel): Everything the engine needs is captured by these three components: -* **`rqs_input`** — the workload model (how traffic is generated). -* **`topology_graph`** — the system under test, described as a directed graph (nodes & edges). +* **`rqs_input`** — workload model (how traffic is generated). +* **`topology_graph`** — system under test as a directed graph (nodes & edges). * **`sim_settings`** — global simulation controls and which metrics to collect. --- -## Why this shape? (Rationale) +## Rationale -### 1) **Separation of concerns** +### 1) Separation of concerns -* **Workload** (traffic intensity & arrival process) is independent from the **topology** (architecture under test) and from **simulation control** (duration & metrics). -* This lets you reuse the same topology with different workloads, or vice versa, without touching unrelated parts. +* **Workload** (traffic intensity & arrival process) is independent from **topology** (architecture) and **simulation control** (duration & metrics). +* You can reuse the same topology with different workloads (or vice versa) without touching unrelated parts. -### 2) **Validation-first, fail-fast** +### 2) Validation-first, fail-fast -* All inputs are **typed** and **validated** with Pydantic before the engine starts. -* Validation catches type errors, inconsistent references, and illegal combinations (e.g., an I/O step with a CPU metric). -* When a payload parses successfully, the engine can run without defensive checks scattered in runtime code. +* Inputs are **typed** and **validated** before the engine starts. +* Validation catches type errors, dangling references, illegal step definitions, and inconsistent graphs. +* Once a payload parses, the runtime code can remain lean (no defensive checks scattered everywhere). -### 3) **Small-to-large composition** +### 3) Small-to-large composition * The smallest unit is a **`Step`** (one resource-bound operation). -* Steps compose into an **`Endpoint`** (an ordered workflow). +* Steps compose into an **`Endpoint`** (ordered workflow). * Endpoints live on a **`Server`** node with finite resources. * Nodes and **Edges** form a **`TopologyGraph`**. -* A disciplined set of **Enums** (no magic strings) ensure a closed vocabulary. +* A closed set of **Enums** eliminates magic strings. --- -## 1. Workload: `RqsGeneratorInput` +## 1) Workload: `RqsGeneratorInput` -### Purpose - -Defines the traffic generator that produces request arrivals. +**Purpose:** Defines the stochastic traffic generator that produces request arrivals. ```python class RqsGeneratorInput(BaseModel): @@ -55,7 +53,11 @@ class RqsGeneratorInput(BaseModel): type: SystemNodes = SystemNodes.GENERATOR avg_active_users: RVConfig avg_request_per_minute_per_user: RVConfig - user_sampling_window: int = Field( ... ) # seconds + user_sampling_window: int = Field( + default=TimeDefaults.USER_SAMPLING_WINDOW, + ge=TimeDefaults.MIN_USER_SAMPLING_WINDOW, + le=TimeDefaults.MAX_USER_SAMPLING_WINDOW, + ) ``` ### Random variables (`RVConfig`) @@ -67,29 +69,24 @@ class RVConfig(BaseModel): variance: float | None = None ``` -#### Validation & guarantees - -* **`mean` is numeric** - `@field_validator("mean", mode="before")` coerces to `float` and rejects non-numeric values. -* **Auto variance** for Normal/LogNormal - `@model_validator(mode="after")` sets `variance = mean` if missing and the distribution is `NORMAL` or `LOG_NORMAL`. -* **Distribution constraints** on workload: +**Validators & guarantees** - * `avg_request_per_minute_per_user` **must be Poisson** (engine currently optimised for Poisson arrivals). - * `avg_active_users` **must be Poisson or Normal**. - * Enforced via `@field_validator(..., mode="after")` with clear error messages. +* `mean` is **numeric** and coerced to `float`. (Non-numeric → `ValueError`.) +* If `distribution ∈ {NORMAL, LOG_NORMAL}` and `variance is None`, then `variance := mean`. +* Workload-specific constraints: -#### Why these constraints? + * `avg_request_per_minute_per_user.distribution` **must be** `POISSON`. + * `avg_active_users.distribution` **must be** `POISSON` or `NORMAL`. +* `user_sampling_window` is an **integer in seconds**, bounded to `[1, 120]`. -* They reflect the current joint-sampling logic in the generator: **Poisson–Poisson** and **Normal–Poisson** are implemented and tested. Additional combos can be enabled later without changing the public contract. +**Why these constraints?** +They match the currently implemented samplers (Poisson–Poisson and Normal–Poisson). --- -## 2. System Graph: `TopologyGraph` - -### Purpose +## 2) System Graph: `TopologyGraph` -Defines the architecture under test as a **directed graph**: nodes are components (client, server, optional load balancer), edges are network links with latency models. +**Purpose:** Describes the architecture as a **directed graph**. Nodes are macro-components (client, server, optional load balancer); edges are network links with latency models. ```python class TopologyGraph(BaseModel): @@ -97,13 +94,15 @@ class TopologyGraph(BaseModel): edges: list[Edge] ``` -### Nodes +### 2.1 Nodes ```python class TopologyNodes(BaseModel): servers: list[Server] client: Client load_balancer: LoadBalancer | None = None + + # also: model_config = ConfigDict(extra="forbid") ``` #### `Client` @@ -112,23 +111,23 @@ class TopologyNodes(BaseModel): class Client(BaseModel): id: str type: SystemNodes = SystemNodes.CLIENT + # validator: type must equal SystemNodes.CLIENT ``` -* **Validator**: `type` must equal `SystemNodes.CLIENT`. - #### `ServerResources` ```python class ServerResources(BaseModel): - cpu_cores: PositiveInt = Field(...) - db_connection_pool: PositiveInt | None = Field(...) - ram_mb: PositiveInt = Field(...) + cpu_cores: PositiveInt = Field(ServerResourcesDefaults.CPU_CORES, + ge=ServerResourcesDefaults.MINIMUM_CPU_CORES) + db_connection_pool: PositiveInt | None = Field(ServerResourcesDefaults.DB_CONNECTION_POOL) + ram_mb: PositiveInt = Field(ServerResourcesDefaults.RAM_MB, + ge=ServerResourcesDefaults.MINIMUM_RAM_MB) ``` -* Maps directly to SimPy containers (CPU tokens, RAM capacity, etc.). -* Bounds enforced via `Field(ge=..., ...)`. +Each attribute maps directly to a SimPy primitive (core tokens, RAM container, optional DB pool). -#### `Step` (the atomic unit) +#### `Step` (atomic unit) ```python class Step(BaseModel): @@ -136,20 +135,15 @@ class Step(BaseModel): step_operation: dict[StepOperation, PositiveFloat | PositiveInt] ``` -**Key validator (coherence):** - -```python -@model_validator(mode="after") -def ensure_coherence_type_operation(cls, model: "Step") -> "Step": - # exactly one operation key, and it must match the step kind -``` +**Coherence validator** -* If `kind` is CPU → the only key must be `CPU_TIME`. -* If `kind` is RAM → only `NECESSARY_RAM`. -* If `kind` is I/O → only `IO_WAITING_TIME`. -* Values must be positive (`PositiveFloat/PositiveInt`). +* `step_operation` must contain **exactly one** key. +* Valid pairings: -This guarantees every step is **unambiguous** and **physically meaningful**. + * CPU step → `{ cpu_time: PositiveFloat }` + * RAM step → `{ necessary_ram: PositiveInt | PositiveFloat }` + * I/O step → `{ io_waiting_time: PositiveFloat }` +* Any mismatch (e.g., RAM step with `cpu_time`) → `ValueError`. #### `Endpoint` @@ -159,11 +153,10 @@ class Endpoint(BaseModel): steps: list[Step] @field_validator("endpoint_name", mode="before") - def name_to_lower(cls, v: str) -> str: - return v.lower() + def name_to_lower(cls, v): return v.lower() ``` -* Canonical lowercase naming avoids duplicates differing only by case. +Canonical lowercase names avoid accidental duplicates by case. #### `Server` @@ -173,10 +166,9 @@ class Server(BaseModel): type: SystemNodes = SystemNodes.SERVER server_resources: ServerResources endpoints: list[Endpoint] + # validator: type must equal SystemNodes.SERVER ``` -* **Validator**: `type` must equal `SystemNodes.SERVER`. - #### `LoadBalancer` (optional) ```python @@ -185,165 +177,153 @@ class LoadBalancer(BaseModel): type: SystemNodes = SystemNodes.LOAD_BALANCER algorithms: LbAlgorithmsName = LbAlgorithmsName.ROUND_ROBIN server_covered: set[str] = Field(default_factory=set) + # validator: type must equal SystemNodes.LOAD_BALANCER ``` -### Edges +### 2.2 Edges ```python class Edge(BaseModel): id: str - source: str - target: str + source: str # may be an external entrypoint (e.g., generator id) + target: str # MUST be a declared node id latency: RVConfig - probability: float = Field(1.0, ge=0.0, le=1.0) edge_type: SystemEdges = SystemEdges.NETWORK_CONNECTION - dropout_rate: float = Field(...) + dropout_rate: float = Field(NetworkParameters.DROPOUT_RATE, + ge=NetworkParameters.MIN_DROPOUT_RATE, + le=NetworkParameters.MAX_DROPOUT_RATE) + # validator: source != target + # validator on latency: mean > 0, variance >= 0 if provided ``` -#### Validation & guarantees +> **Note:** The former `probability` field has been **removed**. Fan-out is controlled at the **load balancer** via `algorithms` (e.g., round-robin, least-connections). Non-LB nodes are not allowed to have multiple outgoing edges (see graph-level validators below). + +### 2.3 Graph-level validators + +The `TopologyGraph` class performs several global checks: + +1. **Unique edge IDs** + + * Duplicate edge ids → `ValueError`. + +2. **Referential integrity** + + * Every **`target`** must be a declared node (`client`, any `server`, optional `load_balancer`). + * **External IDs** (e.g., generator id) are **allowed only as sources** and **must never appear as a target** anywhere. -* **Latency sanity** - `@field_validator("latency", mode="after")` ensures `mean > 0` and `variance >= 0` (if provided). Error messages mention the **edge id** for clarity. -* **No self-loops** - `@model_validator(mode="after")` rejects `source == target`. -* **Unique edge IDs** - `TopologyGraph.unique_ids` enforces uniqueness across `edges`. -* **Referential integrity** - `TopologyGraph.edge_refs_valid` ensures: +3. **Load balancer integrity (if present)** - * Every `target` is a declared node ID. - * **External sources** (e.g., the generator id) are allowed, but **may not** appear as a `target` anywhere. -* **Load balancer integrity** (if present) - `TopologyGraph.valid_load_balancer` enforces: + * `server_covered ⊆ declared server ids`. + * There must be **an outgoing edge from the LB to every covered server**; missing links → `ValueError`. - * `server_covered ⊆ {server ids}`. - * For every covered server there exists an **outgoing edge from the LB** to that server. +4. **Fan-out restriction** -These checks make the graph **closed**, **consistent**, and **wirable** without surprises at runtime. + * Among **declared nodes**, **only the load balancer** may have **multiple outgoing edges**. + * Edges originating from non-declared external sources (e.g., generator) are ignored by this check. + * Violations list the offending source ids. --- -## 3. Simulation Control: `SimulationSettings` +## 3) Simulation Control: `SimulationSettings` ```python class SimulationSettings(BaseModel): - total_simulation_time: int = Field(..., ge=TimeDefaults.MIN_SIMULATION_TIME) + total_simulation_time: int = Field( + default=TimeDefaults.SIMULATION_TIME, + ge=TimeDefaults.MIN_SIMULATION_TIME, + ) enabled_sample_metrics: set[SampledMetricName] = Field(default_factory=...) enabled_event_metrics: set[EventMetricName] = Field(default_factory=...) - sample_period_s: float = Field(..., ge=SamplePeriods.MINIMUM_TIME, le=SamplePeriods.MAXIMUM_TIME) + sample_period_s: float = Field( + default=SamplePeriods.STANDARD_TIME, + ge=SamplePeriods.MINIMUM_TIME, + le=SamplePeriods.MAXIMUM_TIME, + ) ``` -### What it controls +**What it controls** -* **Clock** — `total_simulation_time` (seconds). -* **Sampling cadence** — `sample_period_s` for time-series metrics. -* **What to collect** — two sets of enums: +* **Clock** — `total_simulation_time` in seconds (default 3600, min 5). +* **Sampling cadence** — `sample_period_s` in seconds (default 0.01; bounds `[0.001, 0.1]`). +* **Metric selection** — default sets include: - * `enabled_sample_metrics`: time-series KPIs (e.g., ready queue length, RAM in use, edge concurrency). - * `enabled_event_metrics`: per-event KPIs (e.g., request clocks/latency). + * Sampled (time-series): `ready_queue_len`, `event_loop_io_sleep`, `ram_in_use`, `edge_concurrent_connection`. + * Event (per-request): `rqs_clock`. -### Why Enums matter here +--- -Letting users pass strings like `"ram_in_use"` is error-prone. By using **`SampledMetricName`** and **`EventMetricName`** enums, the settings are **validated upfront**, so the runtime collector knows exactly which lists to allocate and fill. No hidden `KeyError`s halfway through a run. +## 4) Enums & Units (Quick Reference) ---- +**Distributions:** `poisson`, `normal`, `log_normal`, `exponential`, `uniform` +**Node types:** `generator`, `server`, `client`, `load_balancer` (fixed by models) +**Edge types:** `network_connection` +**LB algorithms:** `round_robin`, `least_connection` +**Step kinds:** -## What these validations buy you +* CPU: `initial_parsing`, `cpu_bound_operation` +* RAM: `ram` +* I/O: `io_task_spawn`, `io_llm`, `io_wait`, `io_db`, `io_cache` + **Step operation keys:** `cpu_time`, `io_waiting_time`, `necessary_ram` + **Sampled metrics:** `ready_queue_len`, `event_loop_io_sleep`, `ram_in_use`, `edge_concurrent_connection` + **Event metrics:** `rqs_clock` (and `llm_cost` reserved) -* **Type safety** (no accidental strings where enums are expected). -* **Physical realism** (no zero/negative times or RAM). -* **Graph integrity** (no dangling edges or self-loops). -* **Operational clarity** (every step has exactly one effect). -* **Better errors** (validators point to the exact field/entity at fault). +**Units & conventions** -Together, they make the model **predictable** for the simulation engine and **pleasant** to debug. +* **Time:** seconds (`cpu_time`, `io_waiting_time`, latencies, `total_simulation_time`, `sample_period_s`, `user_sampling_window`) +* **RAM:** megabytes (`ram_mb`, `necessary_ram`) +* **Rates:** requests/minute (`avg_request_per_minute_per_user.mean`) +* **Probabilities:** `[0.0, 1.0]` (`dropout_rate`) +* **IDs:** strings; must be **unique** within their category --- -## End-to-End Example (YAML) - -This is a complete, valid payload you can load with `SimulationRunner.from_yaml(...)`. - -```yaml -# ─────────────────────────────────────────────────────────────── -# FastSim scenario: generator → client → server → client -# ─────────────────────────────────────────────────────────────── - -rqs_input: - id: rqs-1 - # avg_active_users can be POISSON or NORMAL; mean is required. - avg_active_users: - mean: 100 - distribution: poisson - # must be POISSON (engine constraint) - avg_request_per_minute_per_user: - mean: 20 - distribution: poisson - user_sampling_window: 60 # seconds - -topology_graph: - nodes: - client: - id: client-1 - type: client - servers: - - id: srv-1 - type: server - server_resources: - cpu_cores: 1 - ram_mb: 2048 - # db_connection_pool: 50 # optional - endpoints: - - endpoint_name: /predict - steps: - - kind: ram - step_operation: { necessary_ram: 100 } - - kind: initial_parsing # CPU step (enum in your code) - step_operation: { cpu_time: 0.001 } - - kind: io_wait # I/O step - step_operation: { io_waiting_time: 0.100 } - - edges: - - id: gen-to-client - source: rqs-1 # external source OK - target: client-1 - latency: { mean: 0.003, distribution: exponential } - - - id: client-to-server - source: client-1 - target: srv-1 - latency: { mean: 0.003, distribution: exponential } - - - id: server-to-client - source: srv-1 - target: client-1 - latency: { mean: 0.003, distribution: exponential } - -sim_settings: - total_simulation_time: 500 - sample_period_s: 0.05 - enabled_sample_metrics: - - ready_queue_len - - event_loop_io_sleep - - ram_in_use - - edge_concurrent_connection - enabled_event_metrics: - - rqs_clock -``` +## 5) Validation Checklist (What is guaranteed if the payload parses) - Notes: -> - * `kind` uses the **EndpointStep** enums you’ve defined (e.g., `ram`, `initial_parsing`, `io_wait`). - * The coherence validator ensures that each `kind` uses the correct `step_operation` key and **exactly one** entry. - * The **edge** constraints guarantee a clean, connected, and sensible graph. +### Workload (`RqsGeneratorInput`, `RVConfig`) ---- +* `mean` is numeric (`int|float`) and coerced to `float`. +* If `distribution ∈ {NORMAL, LOG_NORMAL}` and `variance is None` → `variance := mean`. +* `avg_request_per_minute_per_user.distribution == POISSON`. +* `avg_active_users.distribution ∈ {POISSON, NORMAL}`. +* `user_sampling_window ∈ [1, 120]` seconds. +* `type` fields default to the correct enum (`generator`) and are strongly typed. + +### Steps & Endpoints + +* `endpoint_name` is normalized to lowercase. +* Each `Step` has **exactly one** `step_operation` key. +* `Step.kind` and `step_operation` key **must match**: + + * CPU ↔ `cpu_time` + * RAM ↔ `necessary_ram` + * I/O ↔ `io_waiting_time` +* All step operation values are strictly **positive**. + +### Nodes + +* `Client.type == client`, `Server.type == server`, `LoadBalancer.type == load_balancer` (enforced). +* `ServerResources` obey lower bounds: `cpu_cores ≥ 1`, `ram_mb ≥ 256`. +* `TopologyNodes` contains **unique ids** across `client`, `servers[]`, and (optional) `load_balancer`. Duplicates → `ValueError`. +* `TopologyNodes` forbids unknown fields (`extra="forbid"`). + +### Edges + +* **No self-loops:** `source != target`. +* **Latency sanity:** `latency.mean > 0`; if `variance` is provided, `variance ≥ 0`. Error messages reference the **edge id**. +* `dropout_rate ∈ [0, 1]`. + +### Graph (`TopologyGraph`) + +* **Edge ids are unique.** +* **Targets** are always **declared node ids**. +* **External ids** (e.g., generator) are allowed only as **sources**; they must **never** appear as **targets**. +* **Load balancer integrity:** + + * `server_covered` is a subset of declared servers. + * Every covered server has a **corresponding edge from the LB** (LB → srv). Missing links → `ValueError`. +* **Fan-out restriction:** among **declared nodes**, only the **LB** can have **multiple outgoing edges**. Offenders are listed. + +If your payload passes validation, the engine can wire and run the simulation deterministically with consistent semantics. -## Summary -* The **payload** is small but expressive: workload, topology, and settings. -* The **validators** are doing real work: they make illegal states unrepresentable. -* The **enums** keep the contract tight and maintainable. -* Together, they let you move fast **without** breaking the simulation engine. -If you extend the engine (new distributions, step kinds, metrics), you can **keep the same contract** and enrich the enums & validators to preserve the same guarantees. diff --git a/docs/fastsim-docs/simulation_runner.md b/docs/fastsim-docs/simulation_runner.md index 8153e13..300b226 100644 --- a/docs/fastsim-docs/simulation_runner.md +++ b/docs/fastsim-docs/simulation_runner.md @@ -3,7 +3,7 @@ ## **Overview** -The `SimulationRunner` is the **orchestrator** of the FastSim engine. +The `SimulationRunner` is the **orchestrator** of the AsyncFlow engine. Its main responsibility is to: 1. **Build** simulation actors from a structured input (`SimulationPayload`). diff --git a/docs/fastsim_vision.md b/docs/fastsim_vision.md index 7119807..bf5b88a 100644 --- a/docs/fastsim_vision.md +++ b/docs/fastsim_vision.md @@ -1,12 +1,12 @@ -## 1 Why FastSim? +## 1 Why AsyncFlow? -FastAPI + Uvicorn gives Python teams a lightning-fast async stack, yet sizing it for production still means guess-work, costly cloud load-tests or late surprises. **FastSim** fills that gap by becoming a **digital twin** of your actual service: +FastAPI + Uvicorn gives Python teams a lightning-fast async stack, yet sizing it for production still means guess-work, costly cloud load-tests or late surprises. **AsyncFlow** fills that gap by becoming a **digital twin** of your actual service: * It **replays** your FastAPI + Uvicorn event-loop behavior in SimPy, generating exactly the same kinds of asynchronous steps (parsing, CPU work, I/O, LLM calls) that happen in real code. * It **models** your infrastructure primitives—CPU cores (via a SimPy `Resource`), database pools, rate-limiters, even GPU inference quotas—so you can see queue lengths, scheduling delays, resource utilization, and end-to-end latency. * It **outputs** the very metrics you’d scrape in production (p50/p95/p99 latency, ready-queue lag, current & max concurrency, throughput, cost per LLM call), but entirely offline, in seconds. -With FastSim you can ask *“What happens if traffic doubles on Black Friday?”*, *“How many cores to keep p95 < 100 ms?”* or *“Is our LLM-driven endpoint ready for prime time?”*—and get quantitative answers **before** you deploy. +With AsyncFlow you can ask *“What happens if traffic doubles on Black Friday?”*, *“How many cores to keep p95 < 100 ms?”* or *“Is our LLM-driven endpoint ready for prime time?”*—and get quantitative answers **before** you deploy. **Outcome:** data-driven capacity planning, early performance tuning, and far fewer “surprises” once you hit production. @@ -26,7 +26,7 @@ With FastSim you can ask *“What happens if traffic doubles on Black Friday?” ## 3 Who benefits & why (detailed) -| Audience | Pain-point solved | FastSim value | +| Audience | Pain-point solved | AsyncFlow value | | ------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Backend engineers** | Unsure if 4 vCPU container survives a marketing spike | Run *what-if* load, tweak CPU cores / pool size, get p95 & max-concurrency before merging. | | **DevOps / SRE** | Guesswork in capacity planning; cost of over-provisioning | Simulate 1 → N replicas, autoscaler thresholds, DB-pool size; pick the cheapest config meeting SLA. | @@ -38,4 +38,4 @@ With FastSim you can ask *“What happens if traffic doubles on Black Friday?” --- -**Bottom-line:** FastSim turns abstract architecture diagrams into concrete numbers—*before* spinning up expensive cloud environments—so you can build, validate and discuss your designs with full confidence. +**Bottom-line:** AsyncFlow turns abstract architecture diagrams into concrete numbers—*before* spinning up expensive cloud environments—so you can build, validate and discuss your designs with full confidence. diff --git a/docs/pybuilder.md b/docs/pybuilder.md new file mode 100644 index 0000000..19b7b24 --- /dev/null +++ b/docs/pybuilder.md @@ -0,0 +1,458 @@ +# AsyncFlow – Programmatic Input Guide (pybuilder) + +This guide shows how to **build the full simulation input in Python** using the +`AsyncFlow` builder (the “pybuilder”), with the same precision and validation +guarantees as the YAML flow. You’ll see **all components, valid values, units, +constraints, and how validation is enforced**. + +Under the hood, the builder assembles a single `SimulationPayload`: + +```python +SimulationPayload( + rqs_input=RqsGeneratorInput(...), # traffic generator (workload) + topology_graph=TopologyGraph(...), # system architecture as a graph + sim_settings=SimulationSettings(...), # global settings and metrics +) +``` + +Everything is **validated up front** by Pydantic. If something is inconsistent +(e.g., an edge points to a non-existent node), a clear error is raised +**before** running the simulation. + +--- + +## Quick Start (Minimal Example) + +```python +from __future__ import annotations + +import simpy + +from asyncflow.pybuilder.input_builder import AsyncFlow +from asyncflow.runtime.simulation_runner import SimulationRunner +from asyncflow.schemas.full_simulation_input import SimulationPayload +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.endpoint import Endpoint +from asyncflow.schemas.system_topology.full_system_topology import ( + Client, Edge, Server, +) + +# 1) Workload +generator = RqsGeneratorInput( + id="rqs-1", + avg_active_users={"mean": 50, "distribution": "poisson"}, + avg_request_per_minute_per_user={"mean": 30, "distribution": "poisson"}, + user_sampling_window=60, # seconds +) + +# 2) Nodes (client + one server) +client = Client(id="client-1") +endpoint = Endpoint( + endpoint_name="/hello", + probability=1.0, # per-endpoint weight on this server + steps=[ + {"kind": "ram", "step_operation": {"necessary_ram": 32}}, + {"kind": "initial_parsing", "step_operation": {"cpu_time": 0.002}}, + {"kind": "io_wait", "step_operation": {"io_waiting_time": 0.010}}, + ], +) +server = Server( + id="srv-1", + server_resources={"cpu_cores": 1, "ram_mb": 1024}, + endpoints=[endpoint], +) + +# 3) Edges (directed, with latency as RV) +edges = [ + Edge( + id="gen-to-client", + source="rqs-1", # external sources allowed + target="client-1", # targets must be declared nodes + latency={"mean": 0.003, "distribution": "exponential"}, + ), + Edge( + id="client-to-server", + source="client-1", + target="srv-1", + latency={"mean": 0.003, "distribution": "exponential"}, + ), + Edge( + id="server-to-client", + source="srv-1", + target="client-1", + latency={"mean": 0.003, "distribution": "exponential"}, + ), +] + +# 4) Global settings +settings = SimulationSettings( + total_simulation_time=300, # seconds, min 5 + sample_period_s=0.01, # seconds, [0.001 .. 0.1] + enabled_sample_metrics=[ + "ready_queue_len", + "event_loop_io_sleep", + "ram_in_use", + "edge_concurrent_connection", + ], + enabled_event_metrics=["rqs_clock"], +) + +# 5) Build (validates everything) +payload: SimulationPayload = ( + AsyncFlow() + .add_generator(generator) + .add_client(client) + .add_servers(server) + .add_edges(*edges) + .add_simulation_settings(settings) + .build_payload() +) + +# 6) Run +env = simpy.Environment() +results = SimulationRunner(env=env, simulation_input=payload).run() +``` + +--- + +## 1) Random Variables (`RVConfig`) + +Where a parameter is stochastic (e.g., edge latency, users, RPM), you pass a +dictionary that Pydantic converts into an `RVConfig`: + +```python +{"mean": , "distribution": , "variance": } +``` + +### Supported distributions + +* `"poisson"` +* `"normal"` +* `"log_normal"` +* `"exponential"` +* `"uniform"` + +### Rules & defaults + +* `mean` is **required** and numeric; coerced to `float`. +* If `distribution` is `"normal"` or `"log_normal"` and `variance` is absent, + it defaults to **`variance = mean`**. +* For **edge latency** (see §3.3): **`mean > 0`** and, if provided, + **`variance ≥ 0`**. + +**Units** + +* Time values are **seconds**. +* Rates are **requests per minute** (where noted). + +--- + +## 2) Workload: `RqsGeneratorInput` + +```python +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput + +generator = RqsGeneratorInput( + id="rqs-1", + avg_active_users={ + "mean": 100, + "distribution": "poisson", # or "normal" + # "variance": , # optional; auto=mean if "normal" + }, + avg_request_per_minute_per_user={ + "mean": 20, + "distribution": "poisson", # must be poisson in current samplers + }, + user_sampling_window=60, # [1 .. 120] seconds +) +``` + +**Semantics** + +* `avg_active_users`: active users as a random variable (Poisson or Normal). +* `avg_request_per_minute_per_user`: per-user RPM (Poisson). +* `user_sampling_window`: re-sample active users every N seconds. + +--- + +## 3) System Topology (`Client`, `Server`, `LoadBalancer`, `Edge`) + +Represent the system as a **directed graph**: nodes (client, servers, optional +LB) and edges (network links). + +### 3.1 Client + +```python +from asyncflow.schemas.system_topology.full_system_topology import Client + +client = Client(id="client-1") # type is fixed to 'client' +``` + +### 3.2 Server & Endpoints + +```python +from asyncflow.schemas.system_topology.endpoint import Endpoint +from asyncflow.schemas.system_topology.full_system_topology import Server + +endpoint = Endpoint( + endpoint_name="/api", # normalized to lowercase internally + probability=1.0, # endpoint selection weight within the server + steps=[ + {"kind": "ram", "step_operation": {"necessary_ram": 64}}, + {"kind": "cpu_bound_operation", "step_operation": {"cpu_time": 0.004}}, + {"kind": "io_db", "step_operation": {"io_waiting_time": 0.012}}, + ], +) + +server = Server( + id="srv-1", # type fixed to 'server' + server_resources={ + "cpu_cores": 2, # int ≥ 1 + "ram_mb": 2048, # int ≥ 256 + "db_connection_pool": None, # optional future-use + }, + endpoints=[endpoint], +) +``` + +**Step kinds** (enums) + +* **CPU**: `"initial_parsing"`, `"cpu_bound_operation"` +* **RAM**: `"ram"` +* **I/O**: `"io_task_spawn"`, `"io_llm"`, `"io_wait"`, `"io_db"`, `"io_cache"` + +**Operation keys** (enum `StepOperation`) + +* `cpu_time` (seconds, positive) +* `necessary_ram` (MB, positive int/float) +* `io_waiting_time` (seconds, positive) + +**Validation enforced** + +* Each step’s `step_operation` has **exactly one** entry. +* The operation **must match** the step kind. +* All numeric values **> 0**. + +**Runtime semantics (high level)** + +* RAM is reserved before CPU, then released at the end. +* CPU tokens acquired lazily for consecutive CPU steps; released on I/O. +* I/O waits **do not** hold a CPU core. + +### 3.3 Load Balancer (optional) + +```python +from asyncflow.schemas.system_topology.full_system_topology import LoadBalancer + +lb = LoadBalancer( + id="lb-1", + algorithms="round_robin", # or "least_connection" + server_covered={"srv-1", "srv-2"}, +) +``` + +**LB validation** + +* `server_covered` must be a subset of declared servers. +* You must define **edges from the LB to each covered server** (see below). + +### 3.4 Edges + +```python +from asyncflow.schemas.system_topology.full_system_topology import Edge + +edge = Edge( + id="client-to-srv1", + source="client-1", # may be external only for sources + target="srv-1", # MUST be a declared node + latency={"mean": 0.003, "distribution": "exponential"}, + probability=1.0, # optional [0..1] + edge_type="network_connection", # current default/only + dropout_rate=0.01, # optional [0..1] +) +``` + +**Semantics** + +* `source`: can be an **external** ID for entry points (e.g., `"rqs-1"`). +* `target`: **must** be a declared node (`client`, `server`, `load_balancer`). +* `latency`: random variable; **`mean > 0`**, `variance ≥ 0` (if provided). +* `probability`: used when a node has multiple outgoing edges (fan-out). + If your code enforces “no fan-out except LB”, do **not** create multiple + outgoing edges from nodes other than the LB. +* `dropout_rate`: per-request/packet drop probability on this link. + +--- + +## 4) Global Settings: `SimulationSettings` + +```python +from asyncflow.schemas.simulation_settings_input import SimulationSettings + +settings = SimulationSettings( + total_simulation_time=600, # seconds, default 3600, min 5 + sample_period_s=0.02, # seconds, [0.001 .. 0.1], default 0.01 + enabled_sample_metrics=[ + "ready_queue_len", + "event_loop_io_sleep", + "ram_in_use", + "edge_concurrent_connection", + ], + enabled_event_metrics=[ + "rqs_clock", + # "llm_cost", # optional future accounting + ], +) +``` + +**Notes** + +* Sampled metrics are time-series collected at `sample_period_s`. +* Event metrics are per-event (e.g., per request), not sampled. + +--- + +## 5) Building the Payload with `AsyncFlow` + +```python +from asyncflow.pybuilder.input_builder import AsyncFlow +from asyncflow.schemas.full_simulation_input import SimulationPayload + +flow = ( + AsyncFlow() + .add_generator(generator) + .add_client(client) + .add_servers(server) # varargs; supports multiple + .add_edges(*edges) # varargs; supports multiple + # .add_load_balancer(lb) # optional + .add_simulation_settings(settings) +) + +payload: SimulationPayload = flow.build_payload() +``` + +**What `build_payload()` validates** + +1. **Presence**: generator, client, ≥1 server, ≥1 edge, settings. +2. **Unique IDs**: servers and edges have unique IDs. +3. **Node types**: fixed enums: `client`, `server`, `load_balancer`. +4. **Edge integrity**: every target is a declared node; external IDs allowed only as sources; no self-loops (`source != target`). +5. **Load balancer sanity**: `server_covered ⊆ declared_servers` and there is an edge from the LB to **each** covered server. +6. **(Optional in your codebase)** “No fan-out except LB” validator: multiple + outgoing edges only allowed for the LB. + +If any rule is violated, a **descriptive `ValueError`** pinpoints the problem. + +--- + +## 6) Running the Simulation + +```python +import simpy +from asyncflow.runtime.simulation_runner import SimulationRunner + +env = simpy.Environment() +runner = SimulationRunner(env=env, simulation_input=payload) +results = runner.run() # blocks until total_simulation_time + +# Access results via the ResultsAnalyzer API: +stats = results.get_latency_stats() +ts, rps = results.get_throughput_series() +sampled = results.get_sampled_metrics() +``` + +You can also plot with the analyzer methods: + +```python +from matplotlib import pyplot as plt # optional +fig, axes = plt.subplots(2, 2, figsize=(12, 8)) +results.plot_latency_distribution(axes[0, 0]) +results.plot_throughput(axes[0, 1]) +results.plot_server_queues(axes[1, 0]) +results.plot_ram_usage(axes[1, 1]) +fig.tight_layout() +fig.savefig("single_server_pybuilder.png") +``` + +--- + +## 7) Builder vs YAML: Field Mapping + +| YAML path | Builder (Python) | +| --------------------------------------------- | ---------------------------------------------------------------- | +| `rqs_input.id` | `RqsGeneratorInput(id=...)` | +| `rqs_input.avg_active_users.*` | `RqsGeneratorInput(avg_active_users={...})` | +| `rqs_input.avg_request_per_minute_per_user.*` | `RqsGeneratorInput(avg_request_per_minute_per_user={...})` | +| `rqs_input.user_sampling_window` | `RqsGeneratorInput(user_sampling_window=...)` | +| `topology_graph.nodes.client.id` | `Client(id=...)` | +| `topology_graph.nodes.servers[*]` | `Server(id=..., server_resources={...}, endpoints=[...])` | +| `endpoint.endpoint_name` | `Endpoint(endpoint_name=...)` | +| `endpoint.steps[*]` | `Endpoint(steps=[{"kind": "...","step_operation": {...}}, ...])` | +| `topology_graph.nodes.load_balancer.*` | `LoadBalancer(id=..., algorithms=..., server_covered={...})` | +| `topology_graph.edges[*]` | `Edge(id=..., source=..., target=..., latency={...}, ...)` | +| `sim_settings.*` | `SimulationSettings(...)` | + +--- + +## 8) Common Pitfalls & How to Avoid Them + +* **Mismatched step operations** + A CPU step must use `cpu_time`; an I/O step must use `io_waiting_time`; a RAM + step must use `necessary_ram`. Exactly **one** key per step. + +* **Edge target must be a declared node** + `source` can be external (e.g., `"rqs-1"`), but **no external ID** may ever + appear as a `target`. + +* **Load balancer coverage without edges** + If the LB covers `[srv-1, srv-2]`, you **must** add edges `lb→srv-1` and + `lb→srv-2`. + +* **Latency RV rules on edges** + `mean` must be **> 0**; if `variance` is present, it must be **≥ 0**. + +* **Fan-out rules** + If your codebase enforces “no fan-out except LB”, do not create multiple + outgoing edges from non-LB nodes. If you do allow it, set `probability` + weights so outgoing probabilities per source sum to \~1.0 (or ensure a single + edge per source). + +* **Sampling too coarse** + Large `sample_period_s` may miss short spikes. Lower it to capture bursts + (at the cost of larger time series). + +--- + +## 9) Enums, Units & Conventions (Cheat Sheet) + +* **Distributions**: `"poisson"`, `"normal"`, `"log_normal"`, `"exponential"`, + `"uniform"` +* **Node types**: fixed internally to: `generator`, `server`, `client`, + `load_balancer` +* **Edge type**: `network_connection` +* **LB algorithms**: `"round_robin"`, `"least_connection"` +* **Step kinds** + CPU: `"initial_parsing"`, `"cpu_bound_operation"` + RAM: `"ram"` + I/O: `"io_task_spawn"`, `"io_llm"`, `"io_wait"`, `"io_db"`, `"io_cache"` +* **Step operation keys**: `cpu_time`, `io_waiting_time`, `necessary_ram` +* **Sampled metrics**: `ready_queue_len`, `event_loop_io_sleep`, `ram_in_use`, + `edge_concurrent_connection` +* **Event metrics**: `rqs_clock` (and `llm_cost` reserved for future use) + +**Units & ranges** + +* **Time**: seconds (`cpu_time`, `io_waiting_time`, edge latency means/variance, + `total_simulation_time`, `sample_period_s`, `user_sampling_window`) +* **RAM**: megabytes (`ram_mb`, `necessary_ram`) +* **Rates**: requests/minute (`avg_request_per_minute_per_user.mean`) +* **Probabilities**: `[0.0, 1.0]` (`probability`, `dropout_rate`) +* **Bounds**: `total_simulation_time ≥ 5`, `sample_period_s ∈ [0.001, 0.1]`, + `cpu_cores ≥ 1`, `ram_mb ≥ 256`, numeric step values > 0 + +--- + +With these patterns, you can build any topology that the YAML supports—**fully +programmatically**, with the same strong validation and clear errors on invalid +configurations. diff --git a/docs/yaml_builder.md b/docs/yaml_builder.md index 40f519e..f3d22e3 100644 --- a/docs/yaml_builder.md +++ b/docs/yaml_builder.md @@ -1,6 +1,6 @@ -# FastSim – YAML Input Guide +# AsyncFlow – YAML Input Guide -This guide explains **how to author the simulation YAML** for FastSim, covering every field, valid values, units, constraints, and the validation rules enforced by the Pydantic schemas. +This guide explains **how to author the simulation YAML** for AsyncFlow, covering every field, valid values, units, constraints, and the validation rules enforced by the Pydantic schemas. The YAML you write is parsed into a single model: @@ -264,7 +264,7 @@ sim_settings: ## 5) Graph-level Validation Rules (what’s checked before running) -FastSim validates the entire payload. Key checks include: +AsyncFlow validates the entire payload. Key checks include: 1. **Unique IDs** diff --git a/examples/data/single_server.yml b/examples/data/single_server.yml index c6e2ce6..844b1ad 100644 --- a/examples/data/single_server.yml +++ b/examples/data/single_server.yml @@ -1,5 +1,5 @@ # ─────────────────────────────────────────────────────────────── -# FastSim scenario: generator ➜ client ➜ server ➜ client +# AsyncFlow scenario: generator ➜ client ➜ server ➜ client # ─────────────────────────────────────────────────────────────── # 1. Traffic generator (light load) diff --git a/examples/single_server_pybuilder.png b/examples/single_server_pybuilder.png index e851f7b..fb31345 100644 Binary files a/examples/single_server_pybuilder.png and b/examples/single_server_pybuilder.png differ diff --git a/examples/single_server_pybuilder.py b/examples/single_server_pybuilder.py index 4d8b757..223766f 100644 --- a/examples/single_server_pybuilder.py +++ b/examples/single_server_pybuilder.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Didactic example: build and run a FastSim scenario **without** YAML, +Didactic example: build and run a AsyncFlow scenario **without** YAML, using the 'pybuilder' (AsyncFlow) to assemble the SimulationPayload. Scenario reproduced (same as the previous YAML): @@ -30,26 +30,26 @@ from __future__ import annotations from pathlib import Path -from typing import Dict, Iterable, List, Mapping, Tuple +from typing import Iterable, List, Mapping import numpy as np import simpy -# ── FastSim domain imports ─────────────────────────────────────────────────── -from app.pybuilder.input_builder import AsyncFlow -from app.runtime.simulation_runner import SimulationRunner -from app.metrics.analyzer import ResultsAnalyzer -from app.schemas.full_simulation_input import SimulationPayload -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.endpoint import Endpoint -from app.schemas.system_topology.full_system_topology import ( +# ── AsyncFlow domain imports ─────────────────────────────────────────────────── +from asyncflow.pybuilder.input_builder import AsyncFlow +from asyncflow.runtime.simulation_runner import SimulationRunner +from asyncflow.metrics.analyzer import ResultsAnalyzer +from asyncflow.schemas.full_simulation_input import SimulationPayload +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.endpoint import Endpoint +from asyncflow.schemas.system_topology.full_system_topology import ( Client, Edge, Server, ) -from app.config.constants import LatencyKey, SampledMetricName +from asyncflow.config.constants import LatencyKey, SampledMetricName # ───────────────────────────────────────────────────────────── diff --git a/examples/single_server_yaml.py b/examples/single_server_yaml.py index 3057a52..fb20d50 100644 --- a/examples/single_server_yaml.py +++ b/examples/single_server_yaml.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Run a FastSim scenario from a YAML file and print diagnostics. +Run a AsyncFlow scenario from a YAML file and print diagnostics. What it does: - Loads the simulation payload from YAML via `SimulationRunner.from_yaml`. @@ -23,15 +23,15 @@ import matplotlib.pyplot as plt import numpy as np import simpy -from app.config.constants import ( +from asyncflow.config.constants import ( EndpointStepCPU, EndpointStepIO, EndpointStepRAM, LatencyKey, StepOperation, ) -from app.metrics.analyzer import ResultsAnalyzer -from app.runtime.simulation_runner import SimulationRunner +from asyncflow.metrics.analyzer import ResultsAnalyzer +from asyncflow.runtime.simulation_runner import SimulationRunner # ───────────────────────────────────────────────────────────── @@ -233,7 +233,7 @@ def run_sanity_checks(runner: SimulationRunner, res: ResultsAnalyzer) -> None: # ───────────────────────────────────────────────────────────── def main() -> None: """Entry-point: parse args, run simulation, print/plot, sanity-check.""" - parser = ArgumentParser(description="Run FastSim from YAML and print outputs + sanity checks.") + parser = ArgumentParser(description="Run AsyncFlow from YAML and print outputs + sanity checks.") parser.add_argument( "--yaml", type=Path, diff --git a/examples/single_server_yml.png b/examples/single_server_yml.png index 89cca6d..560a4b3 100644 Binary files a/examples/single_server_yml.png and b/examples/single_server_yml.png differ diff --git a/examples/your_example.py b/examples/your_example.py index ca31a05..ea2c3fa 100644 --- a/examples/your_example.py +++ b/examples/your_example.py @@ -3,9 +3,9 @@ import simpy import matplotlib.pyplot as plt -from app.config.constants import LatencyKey -from app.runtime.simulation_runner import SimulationRunner -from app.metrics.analyzer import ResultsAnalyzer +from asyncflow.config.constants import LatencyKey +from asyncflow.runtime.simulation_runner import SimulationRunner +from asyncflow.metrics.analyzer import ResultsAnalyzer def print_latency_stats(res: ResultsAnalyzer) -> None: """Print latency statistics returned by the analyzer.""" diff --git a/poetry.lock b/poetry.lock index f698f6c..9147300 100644 --- a/poetry.lock +++ b/poetry.lock @@ -115,99 +115,99 @@ test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist" [[package]] name = "coverage" -version = "7.10.1" +version = "7.10.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372"}, - {file = "coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b"}, - {file = "coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a"}, - {file = "coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f"}, - {file = "coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440"}, - {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8"}, - {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c"}, - {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc"}, - {file = "coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef"}, - {file = "coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed"}, - {file = "coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f"}, - {file = "coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1"}, - {file = "coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437"}, - {file = "coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7"}, - {file = "coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770"}, - {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262"}, - {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3"}, - {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0"}, - {file = "coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be"}, - {file = "coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c"}, - {file = "coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293"}, - {file = "coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4"}, - {file = "coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e"}, - {file = "coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4"}, - {file = "coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a"}, - {file = "coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe"}, - {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386"}, - {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6"}, - {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f"}, - {file = "coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca"}, - {file = "coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3"}, - {file = "coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4"}, - {file = "coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39"}, - {file = "coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7"}, - {file = "coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892"}, - {file = "coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7"}, - {file = "coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994"}, - {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0"}, - {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7"}, - {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7"}, - {file = "coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7"}, - {file = "coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e"}, - {file = "coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4"}, - {file = "coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72"}, - {file = "coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af"}, - {file = "coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7"}, - {file = "coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759"}, - {file = "coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324"}, - {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53"}, - {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f"}, - {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd"}, - {file = "coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c"}, - {file = "coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18"}, - {file = "coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4"}, - {file = "coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c"}, - {file = "coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e"}, - {file = "coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b"}, - {file = "coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41"}, - {file = "coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f"}, - {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1"}, - {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2"}, - {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4"}, - {file = "coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613"}, - {file = "coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e"}, - {file = "coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652"}, - {file = "coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894"}, - {file = "coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5"}, - {file = "coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2"}, - {file = "coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb"}, - {file = "coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b"}, - {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea"}, - {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd"}, - {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d"}, - {file = "coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47"}, - {file = "coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651"}, - {file = "coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab"}, - {file = "coverage-7.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:57b6e8789cbefdef0667e4a94f8ffa40f9402cee5fc3b8e4274c894737890145"}, - {file = "coverage-7.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85b22a9cce00cb03156334da67eb86e29f22b5e93876d0dd6a98646bb8a74e53"}, - {file = "coverage-7.10.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97b6983a2f9c76d345ca395e843a049390b39652984e4a3b45b2442fa733992d"}, - {file = "coverage-7.10.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ddf2a63b91399a1c2f88f40bc1705d5a7777e31c7e9eb27c602280f477b582ba"}, - {file = "coverage-7.10.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47ab6dbbc31a14c5486420c2c1077fcae692097f673cf5be9ddbec8cdaa4cdbc"}, - {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:21eb7d8b45d3700e7c2936a736f732794c47615a20f739f4133d5230a6512a88"}, - {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:283005bb4d98ae33e45f2861cd2cde6a21878661c9ad49697f6951b358a0379b"}, - {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fefe31d61d02a8b2c419700b1fade9784a43d726de26495f243b663cd9fe1513"}, - {file = "coverage-7.10.1-cp39-cp39-win32.whl", hash = "sha256:e8ab8e4c7ec7f8a55ac05b5b715a051d74eac62511c6d96d5bb79aaafa3b04cf"}, - {file = "coverage-7.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:c36baa0ecde742784aa76c2b816466d3ea888d5297fda0edbac1bf48fa94688a"}, - {file = "coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7"}, - {file = "coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57"}, + {file = "coverage-7.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53808194afdf948c462215e9403cca27a81cf150d2f9b386aee4dab614ae2ffe"}, + {file = "coverage-7.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f4d1b837d1abf72187a61645dbf799e0d7705aa9232924946e1f57eb09a3bf00"}, + {file = "coverage-7.10.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2a90dd4505d3cc68b847ab10c5ee81822a968b5191664e8a0801778fa60459fa"}, + {file = "coverage-7.10.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d52989685ff5bf909c430e6d7f6550937bc6d6f3e6ecb303c97a86100efd4596"}, + {file = "coverage-7.10.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdb558a1d97345bde3a9f4d3e8d11c9e5611f748646e9bb61d7d612a796671b5"}, + {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c9e6331a8f09cb1fc8bda032752af03c366870b48cce908875ba2620d20d0ad4"}, + {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:992f48bf35b720e174e7fae916d943599f1a66501a2710d06c5f8104e0756ee1"}, + {file = "coverage-7.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c5595fc4ad6a39312c786ec3326d7322d0cf10e3ac6a6df70809910026d67cfb"}, + {file = "coverage-7.10.3-cp310-cp310-win32.whl", hash = "sha256:9e92fa1f2bd5a57df9d00cf9ce1eb4ef6fccca4ceabec1c984837de55329db34"}, + {file = "coverage-7.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b96524d6e4a3ce6a75c56bb15dbd08023b0ae2289c254e15b9fbdddf0c577416"}, + {file = "coverage-7.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2ff2e2afdf0d51b9b8301e542d9c21a8d084fd23d4c8ea2b3a1b3c96f5f7397"}, + {file = "coverage-7.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:18ecc5d1b9a8c570f6c9b808fa9a2b16836b3dd5414a6d467ae942208b095f85"}, + {file = "coverage-7.10.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1af4461b25fe92889590d438905e1fc79a95680ec2a1ff69a591bb3fdb6c7157"}, + {file = "coverage-7.10.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3966bc9a76b09a40dc6063c8b10375e827ea5dfcaffae402dd65953bef4cba54"}, + {file = "coverage-7.10.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:205a95b87ef4eb303b7bc5118b47b6b6604a644bcbdb33c336a41cfc0a08c06a"}, + {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b3801b79fb2ad61e3c7e2554bab754fc5f105626056980a2b9cf3aef4f13f84"}, + {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0dc69c60224cda33d384572da945759756e3f06b9cdac27f302f53961e63160"}, + {file = "coverage-7.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a83d4f134bab2c7ff758e6bb1541dd72b54ba295ced6a63d93efc2e20cb9b124"}, + {file = "coverage-7.10.3-cp311-cp311-win32.whl", hash = "sha256:54e409dd64e5302b2a8fdf44ec1c26f47abd1f45a2dcf67bd161873ee05a59b8"}, + {file = "coverage-7.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:30c601610a9b23807c5e9e2e442054b795953ab85d525c3de1b1b27cebeb2117"}, + {file = "coverage-7.10.3-cp311-cp311-win_arm64.whl", hash = "sha256:dabe662312a97958e932dee056f2659051d822552c0b866823e8ba1c2fe64770"}, + {file = "coverage-7.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:449c1e2d3a84d18bd204258a897a87bc57380072eb2aded6a5b5226046207b42"}, + {file = "coverage-7.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d4f9ce50b9261ad196dc2b2e9f1fbbee21651b54c3097a25ad783679fd18294"}, + {file = "coverage-7.10.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4dd4564207b160d0d45c36a10bc0a3d12563028e8b48cd6459ea322302a156d7"}, + {file = "coverage-7.10.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ca3c9530ee072b7cb6a6ea7b640bcdff0ad3b334ae9687e521e59f79b1d0437"}, + {file = "coverage-7.10.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6df359e59fa243c9925ae6507e27f29c46698359f45e568fd51b9315dbbe587"}, + {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a181e4c2c896c2ff64c6312db3bda38e9ade2e1aa67f86a5628ae85873786cea"}, + {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a374d4e923814e8b72b205ef6b3d3a647bb50e66f3558582eda074c976923613"}, + {file = "coverage-7.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:daeefff05993e5e8c6e7499a8508e7bd94502b6b9a9159c84fd1fe6bce3151cb"}, + {file = "coverage-7.10.3-cp312-cp312-win32.whl", hash = "sha256:187ecdcac21f9636d570e419773df7bd2fda2e7fa040f812e7f95d0bddf5f79a"}, + {file = "coverage-7.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:4a50ad2524ee7e4c2a95e60d2b0b83283bdfc745fe82359d567e4f15d3823eb5"}, + {file = "coverage-7.10.3-cp312-cp312-win_arm64.whl", hash = "sha256:c112f04e075d3495fa3ed2200f71317da99608cbb2e9345bdb6de8819fc30571"}, + {file = "coverage-7.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b99e87304ffe0eb97c5308447328a584258951853807afdc58b16143a530518a"}, + {file = "coverage-7.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4af09c7574d09afbc1ea7da9dcea23665c01f3bc1b1feb061dac135f98ffc53a"}, + {file = "coverage-7.10.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:488e9b50dc5d2aa9521053cfa706209e5acf5289e81edc28291a24f4e4488f46"}, + {file = "coverage-7.10.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:913ceddb4289cbba3a310704a424e3fb7aac2bc0c3a23ea473193cb290cf17d4"}, + {file = "coverage-7.10.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b1f91cbc78c7112ab84ed2a8defbccd90f888fcae40a97ddd6466b0bec6ae8a"}, + {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0bac054d45af7cd938834b43a9878b36ea92781bcb009eab040a5b09e9927e3"}, + {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fe72cbdd12d9e0f4aca873fa6d755e103888a7f9085e4a62d282d9d5b9f7928c"}, + {file = "coverage-7.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c1e2e927ab3eadd7c244023927d646e4c15c65bb2ac7ae3c3e9537c013700d21"}, + {file = "coverage-7.10.3-cp313-cp313-win32.whl", hash = "sha256:24d0c13de473b04920ddd6e5da3c08831b1170b8f3b17461d7429b61cad59ae0"}, + {file = "coverage-7.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:3564aae76bce4b96e2345cf53b4c87e938c4985424a9be6a66ee902626edec4c"}, + {file = "coverage-7.10.3-cp313-cp313-win_arm64.whl", hash = "sha256:f35580f19f297455f44afcd773c9c7a058e52eb6eb170aa31222e635f2e38b87"}, + {file = "coverage-7.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07009152f497a0464ffdf2634586787aea0e69ddd023eafb23fc38267db94b84"}, + {file = "coverage-7.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd2ba5f0c7e7e8cc418be2f0c14c4d9e3f08b8fb8e4c0f83c2fe87d03eb655e"}, + {file = "coverage-7.10.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ae22b97003c74186e034a93e4f946c75fad8c0ce8d92fbbc168b5e15ee2841f"}, + {file = "coverage-7.10.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb329f1046888a36b1dc35504d3029e1dd5afe2196d94315d18c45ee380f67d5"}, + {file = "coverage-7.10.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce01048199a91f07f96ca3074b0c14021f4fe7ffd29a3e6a188ac60a5c3a4af8"}, + {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08b989a06eb9dfacf96d42b7fb4c9a22bafa370d245dc22fa839f2168c6f9fa1"}, + {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:669fe0d4e69c575c52148511029b722ba8d26e8a3129840c2ce0522e1452b256"}, + {file = "coverage-7.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3262d19092771c83f3413831d9904b1ccc5f98da5de4ffa4ad67f5b20c7aaf7b"}, + {file = "coverage-7.10.3-cp313-cp313t-win32.whl", hash = "sha256:cc0ee4b2ccd42cab7ee6be46d8a67d230cb33a0a7cd47a58b587a7063b6c6b0e"}, + {file = "coverage-7.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:03db599f213341e2960430984e04cf35fb179724e052a3ee627a068653cf4a7c"}, + {file = "coverage-7.10.3-cp313-cp313t-win_arm64.whl", hash = "sha256:46eae7893ba65f53c71284585a262f083ef71594f05ec5c85baf79c402369098"}, + {file = "coverage-7.10.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bce8b8180912914032785850d8f3aacb25ec1810f5f54afc4a8b114e7a9b55de"}, + {file = "coverage-7.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07790b4b37d56608536f7c1079bd1aa511567ac2966d33d5cec9cf520c50a7c8"}, + {file = "coverage-7.10.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e79367ef2cd9166acedcbf136a458dfe9a4a2dd4d1ee95738fb2ee581c56f667"}, + {file = "coverage-7.10.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:419d2a0f769f26cb1d05e9ccbc5eab4cb5d70231604d47150867c07822acbdf4"}, + {file = "coverage-7.10.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee221cf244757cdc2ac882e3062ab414b8464ad9c884c21e878517ea64b3fa26"}, + {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c2079d8cdd6f7373d628e14b3357f24d1db02c9dc22e6a007418ca7a2be0435a"}, + {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bd8df1f83c0703fa3ca781b02d36f9ec67ad9cb725b18d486405924f5e4270bd"}, + {file = "coverage-7.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6b4e25e0fa335c8aa26e42a52053f3786a61cc7622b4d54ae2dad994aa754fec"}, + {file = "coverage-7.10.3-cp314-cp314-win32.whl", hash = "sha256:d7c3d02c2866deb217dce664c71787f4b25420ea3eaf87056f44fb364a3528f5"}, + {file = "coverage-7.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:9c8916d44d9e0fe6cdb2227dc6b0edd8bc6c8ef13438bbbf69af7482d9bb9833"}, + {file = "coverage-7.10.3-cp314-cp314-win_arm64.whl", hash = "sha256:1007d6a2b3cf197c57105cc1ba390d9ff7f0bee215ced4dea530181e49c65ab4"}, + {file = "coverage-7.10.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ebc8791d346410d096818788877d675ca55c91db87d60e8f477bd41c6970ffc6"}, + {file = "coverage-7.10.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f4e4d8e75f6fd3c6940ebeed29e3d9d632e1f18f6fb65d33086d99d4d073241"}, + {file = "coverage-7.10.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:24581ed69f132b6225a31b0228ae4885731cddc966f8a33fe5987288bdbbbd5e"}, + {file = "coverage-7.10.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec151569ddfccbf71bac8c422dce15e176167385a00cd86e887f9a80035ce8a5"}, + {file = "coverage-7.10.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ae8e7c56290b908ee817200c0b65929b8050bc28530b131fe7c6dfee3e7d86b"}, + {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fb742309766d7e48e9eb4dc34bc95a424707bc6140c0e7d9726e794f11b92a0"}, + {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c65e2a5b32fbe1e499f1036efa6eb9cb4ea2bf6f7168d0e7a5852f3024f471b1"}, + {file = "coverage-7.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d48d2cb07d50f12f4f18d2bb75d9d19e3506c26d96fffabf56d22936e5ed8f7c"}, + {file = "coverage-7.10.3-cp314-cp314t-win32.whl", hash = "sha256:dec0d9bc15ee305e09fe2cd1911d3f0371262d3cfdae05d79515d8cb712b4869"}, + {file = "coverage-7.10.3-cp314-cp314t-win_amd64.whl", hash = "sha256:424ea93a323aa0f7f01174308ea78bde885c3089ec1bef7143a6d93c3e24ef64"}, + {file = "coverage-7.10.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f5983c132a62d93d71c9ef896a0b9bf6e6828d8d2ea32611f58684fba60bba35"}, + {file = "coverage-7.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da749daa7e141985487e1ff90a68315b0845930ed53dc397f4ae8f8bab25b551"}, + {file = "coverage-7.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3126fb6a47d287f461d9b1aa5d1a8c97034d1dffb4f452f2cf211289dae74ef"}, + {file = "coverage-7.10.3-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3da794db13cc27ca40e1ec8127945b97fab78ba548040047d54e7bfa6d442dca"}, + {file = "coverage-7.10.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4e27bebbd184ef8d1c1e092b74a2b7109dcbe2618dce6e96b1776d53b14b3fe8"}, + {file = "coverage-7.10.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fd4ee2580b9fefbd301b4f8f85b62ac90d1e848bea54f89a5748cf132782118"}, + {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6999920bdd73259ce11cabfc1307484f071ecc6abdb2ca58d98facbcefc70f16"}, + {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3623f929db885fab100cb88220a5b193321ed37e03af719efdbaf5d10b6e227"}, + {file = "coverage-7.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:25b902c5e15dea056485d782e420bb84621cc08ee75d5131ecb3dbef8bd1365f"}, + {file = "coverage-7.10.3-cp39-cp39-win32.whl", hash = "sha256:f930a4d92b004b643183451fe9c8fe398ccf866ed37d172ebaccfd443a097f61"}, + {file = "coverage-7.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:08e638a93c8acba13c7842953f92a33d52d73e410329acd472280d2a21a6c0e1"}, + {file = "coverage-7.10.3-py3-none-any.whl", hash = "sha256:416a8d74dc0adfd33944ba2f405897bab87b7e9e84a391e09d241956bd953ce1"}, + {file = "coverage-7.10.3.tar.gz", hash = "sha256:812ba9250532e4a823b070b0420a36499859542335af3dca8f47fc6aa1a05619"}, ] [package.extras] @@ -354,134 +354,176 @@ files = [ [[package]] name = "kiwisolver" -version = "1.4.8" +version = "1.4.9" description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" files = [ - {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, - {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, - {file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d"}, - {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d"}, - {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c"}, - {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3"}, - {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed"}, - {file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f"}, - {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff"}, - {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d"}, - {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c"}, - {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605"}, - {file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e"}, - {file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751"}, - {file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271"}, - {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84"}, - {file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561"}, - {file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7"}, - {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03"}, - {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954"}, - {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79"}, - {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6"}, - {file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0"}, - {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab"}, - {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc"}, - {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25"}, - {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc"}, - {file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67"}, - {file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34"}, - {file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2"}, - {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502"}, - {file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31"}, - {file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb"}, - {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f"}, - {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc"}, - {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a"}, - {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a"}, - {file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a"}, - {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3"}, - {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b"}, - {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4"}, - {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d"}, - {file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8"}, - {file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50"}, - {file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476"}, - {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09"}, - {file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1"}, - {file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c"}, - {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b"}, - {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47"}, - {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16"}, - {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc"}, - {file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246"}, - {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794"}, - {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b"}, - {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3"}, - {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957"}, - {file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb"}, - {file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2"}, - {file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90"}, - {file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85"}, - {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a"}, - {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8"}, - {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0"}, - {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c"}, - {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b"}, - {file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b"}, - {file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f"}, + {file = "kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b"}, + {file = "kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586"}, + {file = "kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611"}, + {file = "kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089"}, + {file = "kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872"}, + {file = "kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a"}, + {file = "kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2"}, + {file = "kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"}, + {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"}, + {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"}, + {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"}, + {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"}, + {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"}, + {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"}, + {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"}, + {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"}, + {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"}, + {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"}, + {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"}, + {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"}, + {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9"}, + {file = "kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f"}, + {file = "kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1"}, + {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"}, ] [[package]] name = "matplotlib" -version = "3.10.3" +version = "3.10.5" description = "Python plotting package" optional = false python-versions = ">=3.10" files = [ - {file = "matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7"}, - {file = "matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb"}, - {file = "matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb"}, - {file = "matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30"}, - {file = "matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8"}, - {file = "matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd"}, - {file = "matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8"}, - {file = "matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d"}, - {file = "matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049"}, - {file = "matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b"}, - {file = "matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220"}, - {file = "matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1"}, - {file = "matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea"}, - {file = "matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4"}, - {file = "matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee"}, - {file = "matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a"}, - {file = "matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7"}, - {file = "matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05"}, - {file = "matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84"}, - {file = "matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e"}, - {file = "matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15"}, - {file = "matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7"}, - {file = "matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d"}, - {file = "matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93"}, - {file = "matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2"}, - {file = "matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d"}, - {file = "matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566"}, - {file = "matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158"}, - {file = "matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d"}, - {file = "matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5"}, - {file = "matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4"}, - {file = "matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751"}, - {file = "matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014"}, - {file = "matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0"}, + {file = "matplotlib-3.10.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5d4773a6d1c106ca05cb5a5515d277a6bb96ed09e5c8fab6b7741b8fcaa62c8f"}, + {file = "matplotlib-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc88af74e7ba27de6cbe6faee916024ea35d895ed3d61ef6f58c4ce97da7185a"}, + {file = "matplotlib-3.10.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64c4535419d5617f7363dad171a5a59963308e0f3f813c4bed6c9e6e2c131512"}, + {file = "matplotlib-3.10.5-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a277033048ab22d34f88a3c5243938cef776493f6201a8742ed5f8b553201343"}, + {file = "matplotlib-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e4a6470a118a2e93022ecc7d3bd16b3114b2004ea2bf014fff875b3bc99b70c6"}, + {file = "matplotlib-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:7e44cada61bec8833c106547786814dd4a266c1b2964fd25daa3804f1b8d4467"}, + {file = "matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf"}, + {file = "matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf"}, + {file = "matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a"}, + {file = "matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc"}, + {file = "matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89"}, + {file = "matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903"}, + {file = "matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420"}, + {file = "matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2"}, + {file = "matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389"}, + {file = "matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea"}, + {file = "matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468"}, + {file = "matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369"}, + {file = "matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b"}, + {file = "matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2"}, + {file = "matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed"}, + {file = "matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1"}, + {file = "matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7"}, + {file = "matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f"}, + {file = "matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4"}, + {file = "matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe"}, + {file = "matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674"}, + {file = "matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c"}, + {file = "matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e"}, + {file = "matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b"}, + {file = "matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea"}, + {file = "matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124"}, + {file = "matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce"}, + {file = "matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154"}, + {file = "matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715"}, + {file = "matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837"}, + {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202"}, + {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb"}, + {file = "matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975"}, + {file = "matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667"}, + {file = "matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516"}, + {file = "matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e"}, + {file = "matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a"}, + {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569"}, + {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012"}, + {file = "matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592"}, + {file = "matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959"}, + {file = "matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b"}, + {file = "matplotlib-3.10.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b5fa2e941f77eb579005fb804026f9d0a1082276118d01cc6051d0d9626eaa7f"}, + {file = "matplotlib-3.10.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1fc0d2a3241cdcb9daaca279204a3351ce9df3c0e7e621c7e04ec28aaacaca30"}, + {file = "matplotlib-3.10.5-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8dee65cb1424b7dc982fe87895b5613d4e691cc57117e8af840da0148ca6c1d7"}, + {file = "matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f"}, + {file = "matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b"}, + {file = "matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61"}, + {file = "matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076"}, ] [package.dependencies] @@ -500,43 +542,49 @@ dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setup [[package]] name = "mypy" -version = "1.17.0" +version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" files = [ - {file = "mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6"}, - {file = "mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d"}, - {file = "mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b"}, - {file = "mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a"}, - {file = "mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f"}, - {file = "mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937"}, - {file = "mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be"}, - {file = "mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61"}, - {file = "mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f"}, - {file = "mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d"}, - {file = "mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3"}, - {file = "mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70"}, - {file = "mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb"}, - {file = "mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d"}, - {file = "mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8"}, - {file = "mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e"}, - {file = "mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8"}, - {file = "mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d"}, - {file = "mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06"}, - {file = "mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a"}, - {file = "mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889"}, - {file = "mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba"}, - {file = "mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658"}, - {file = "mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c"}, - {file = "mypy-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:63e751f1b5ab51d6f3d219fe3a2fe4523eaa387d854ad06906c63883fde5b1ab"}, - {file = "mypy-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fb09d05e0f1c329a36dcd30e27564a3555717cde87301fae4fb542402ddfad"}, - {file = "mypy-1.17.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b72c34ce05ac3a1361ae2ebb50757fb6e3624032d91488d93544e9f82db0ed6c"}, - {file = "mypy-1.17.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434ad499ad8dde8b2f6391ddfa982f41cb07ccda8e3c67781b1bfd4e5f9450a8"}, - {file = "mypy-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f105f61a5eff52e137fd73bee32958b2add9d9f0a856f17314018646af838e97"}, - {file = "mypy-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:ba06254a5a22729853209550d80f94e28690d5530c661f9416a68ac097b13fc4"}, - {file = "mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496"}, - {file = "mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] [package.dependencies] @@ -1140,29 +1188,29 @@ files = [ [[package]] name = "ruff" -version = "0.12.5" +version = "0.12.8" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.12.5-py3-none-linux_armv6l.whl", hash = "sha256:1de2c887e9dec6cb31fcb9948299de5b2db38144e66403b9660c9548a67abd92"}, - {file = "ruff-0.12.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d1ab65e7d8152f519e7dea4de892317c9da7a108da1c56b6a3c1d5e7cf4c5e9a"}, - {file = "ruff-0.12.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:962775ed5b27c7aa3fdc0d8f4d4433deae7659ef99ea20f783d666e77338b8cf"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b4cae449597e7195a49eb1cdca89fd9fbb16140c7579899e87f4c85bf82f73"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b13489c3dc50de5e2d40110c0cce371e00186b880842e245186ca862bf9a1ac"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1504fea81461cf4841778b3ef0a078757602a3b3ea4b008feb1308cb3f23e08"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7da4129016ae26c32dfcbd5b671fe652b5ab7fc40095d80dcff78175e7eddd4"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ca972c80f7ebcfd8af75a0f18b17c42d9f1ef203d163669150453f50ca98ab7b"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dbbf9f25dfb501f4237ae7501d6364b76a01341c6f1b2cd6764fe449124bb2a"}, - {file = "ruff-0.12.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c47dea6ae39421851685141ba9734767f960113d51e83fd7bb9958d5be8763a"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5076aa0e61e30f848846f0265c873c249d4b558105b221be1828f9f79903dc5"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5a4c7830dadd3d8c39b1cc85386e2c1e62344f20766be6f173c22fb5f72f293"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:46699f73c2b5b137b9dc0fc1a190b43e35b008b398c6066ea1350cce6326adcb"}, - {file = "ruff-0.12.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5a655a0a0d396f0f072faafc18ebd59adde8ca85fb848dc1b0d9f024b9c4d3bb"}, - {file = "ruff-0.12.5-py3-none-win32.whl", hash = "sha256:dfeb2627c459b0b78ca2bbdc38dd11cc9a0a88bf91db982058b26ce41714ffa9"}, - {file = "ruff-0.12.5-py3-none-win_amd64.whl", hash = "sha256:ae0d90cf5f49466c954991b9d8b953bd093c32c27608e409ae3564c63c5306a5"}, - {file = "ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805"}, - {file = "ruff-0.12.5.tar.gz", hash = "sha256:b209db6102b66f13625940b7f8c7d0f18e20039bb7f6101fbdac935c9612057e"}, + {file = "ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513"}, + {file = "ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc"}, + {file = "ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46"}, + {file = "ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3"}, + {file = "ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e"}, + {file = "ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749"}, + {file = "ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033"}, ] [[package]] @@ -1189,13 +1237,13 @@ files = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20250516" +version = "6.0.12.20250809" description = "Typing stubs for PyYAML" optional = false python-versions = ">=3.9" files = [ - {file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"}, - {file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"}, + {file = "types_pyyaml-6.0.12.20250809-py3-none-any.whl", hash = "sha256:032b6003b798e7de1a1ddfeefee32fac6486bdfe4845e0ae0e7fb3ee4512b52f"}, + {file = "types_pyyaml-6.0.12.20250809.tar.gz", hash = "sha256:af4a1aca028f18e75297da2ee0da465f799627370d74073e96fee876524f61b5"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 22fcaa2..1348af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [tool.poetry] -name = "FastSim" +name = "AsyncFlow" version = "0.1.0" -description = "Simulate fastapi event loop to manage resources" +description = "Simulate distributed system for what if analysis for the capacity planning" authors = ["Gioele Botta"] readme = "README.md" packages = [ - { include = "app", from = "src" } + { include = "asyncflow", from = "src" } ] [tool.poetry.dependencies] diff --git a/src/app/__init__.py b/src/asyncflow/__init__.py similarity index 100% rename from src/app/__init__.py rename to src/asyncflow/__init__.py diff --git a/src/app/config/__init__.py b/src/asyncflow/config/__init__.py similarity index 100% rename from src/app/config/__init__.py rename to src/asyncflow/config/__init__.py diff --git a/src/app/config/constants.py b/src/asyncflow/config/constants.py similarity index 99% rename from src/app/config/constants.py rename to src/asyncflow/config/constants.py index 0aa5f9d..de79a33 100644 --- a/src/app/config/constants.py +++ b/src/asyncflow/config/constants.py @@ -1,7 +1,7 @@ """ Application-wide constants and configuration values. -This module groups all the static enumerations used by the FastSim backend +This module groups all the static enumerations used by the AsyncFlow backend so that: JSON / YAML payloads can be strictly validated with Pydantic. diff --git a/src/app/config/plot_constants.py b/src/asyncflow/config/plot_constants.py similarity index 100% rename from src/app/config/plot_constants.py rename to src/asyncflow/config/plot_constants.py diff --git a/src/app/metrics/analyzer.py b/src/asyncflow/metrics/analyzer.py similarity index 95% rename from src/app/metrics/analyzer.py rename to src/asyncflow/metrics/analyzer.py index 8308d54..4db2ee1 100644 --- a/src/app/metrics/analyzer.py +++ b/src/asyncflow/metrics/analyzer.py @@ -7,8 +7,8 @@ import numpy as np -from app.config.constants import LatencyKey, SampledMetricName -from app.config.plot_constants import ( +from asyncflow.config.constants import LatencyKey, SampledMetricName +from asyncflow.config.plot_constants import ( LATENCY_PLOT, RAM_PLOT, SERVER_QUEUES_PLOT, @@ -23,10 +23,10 @@ from matplotlib.axes import Axes from matplotlib.lines import Line2D - from app.runtime.actors.client import ClientRuntime - from app.runtime.actors.edge import EdgeRuntime - from app.runtime.actors.server import ServerRuntime - from app.schemas.simulation_settings_input import SimulationSettings + from asyncflow.runtime.actors.client import ClientRuntime + from asyncflow.runtime.actors.edge import EdgeRuntime + from asyncflow.runtime.actors.server import ServerRuntime + from asyncflow.schemas.simulation_settings_input import SimulationSettings class ResultsAnalyzer: diff --git a/src/app/metrics/client.py b/src/asyncflow/metrics/client.py similarity index 100% rename from src/app/metrics/client.py rename to src/asyncflow/metrics/client.py diff --git a/src/app/metrics/collector.py b/src/asyncflow/metrics/collector.py similarity index 91% rename from src/app/metrics/collector.py rename to src/asyncflow/metrics/collector.py index d2158f5..97421b7 100644 --- a/src/app/metrics/collector.py +++ b/src/asyncflow/metrics/collector.py @@ -4,10 +4,10 @@ import simpy -from app.config.constants import SampledMetricName -from app.runtime.actors.edge import EdgeRuntime -from app.runtime.actors.server import ServerRuntime -from app.schemas.simulation_settings_input import SimulationSettings +from asyncflow.config.constants import SampledMetricName +from asyncflow.runtime.actors.edge import EdgeRuntime +from asyncflow.runtime.actors.server import ServerRuntime +from asyncflow.schemas.simulation_settings_input import SimulationSettings # The idea for this class is to gather list of runtime objects that # are defined in the central class to build the simulation, in this diff --git a/src/app/metrics/edge.py b/src/asyncflow/metrics/edge.py similarity index 94% rename from src/app/metrics/edge.py rename to src/asyncflow/metrics/edge.py index f714482..f9626dd 100644 --- a/src/app/metrics/edge.py +++ b/src/asyncflow/metrics/edge.py @@ -2,7 +2,7 @@ from collections.abc import Iterable -from app.config.constants import SampledMetricName +from asyncflow.config.constants import SampledMetricName # Initialize one time outside the function all possible metrics # related to the edges, the idea of this structure is to diff --git a/src/app/metrics/server.py b/src/asyncflow/metrics/server.py similarity index 94% rename from src/app/metrics/server.py rename to src/asyncflow/metrics/server.py index 18810ed..6ebb96e 100644 --- a/src/app/metrics/server.py +++ b/src/asyncflow/metrics/server.py @@ -5,7 +5,7 @@ from collections.abc import Iterable -from app.config.constants import SampledMetricName +from asyncflow.config.constants import SampledMetricName # Initialize one time outside the function all possible metrics # related to the servers, the idea of this structure is to diff --git a/src/app/pybuilder/input_builder.py b/src/asyncflow/pybuilder/input_builder.py similarity index 93% rename from src/app/pybuilder/input_builder.py rename to src/asyncflow/pybuilder/input_builder.py index 223cd32..7d3c26f 100644 --- a/src/app/pybuilder/input_builder.py +++ b/src/asyncflow/pybuilder/input_builder.py @@ -4,10 +4,10 @@ from typing import Self -from app.schemas.full_simulation_input import SimulationPayload -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.full_system_topology import ( +from asyncflow.schemas.full_simulation_input import SimulationPayload +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.full_system_topology import ( Client, Edge, LoadBalancer, diff --git a/src/app/resources/__init__.py b/src/asyncflow/resources/__init__.py similarity index 100% rename from src/app/resources/__init__.py rename to src/asyncflow/resources/__init__.py diff --git a/src/app/resources/registry.py b/src/asyncflow/resources/registry.py similarity index 86% rename from src/app/resources/registry.py rename to src/asyncflow/resources/registry.py index 02b7585..6e1e3be 100644 --- a/src/app/resources/registry.py +++ b/src/asyncflow/resources/registry.py @@ -9,8 +9,8 @@ import simpy -from app.resources.server_containers import ServerContainers, build_containers -from app.schemas.system_topology.full_system_topology import TopologyGraph +from asyncflow.resources.server_containers import ServerContainers, build_containers +from asyncflow.schemas.system_topology.full_system_topology import TopologyGraph class ResourcesRuntime: diff --git a/src/app/resources/server_containers.py b/src/asyncflow/resources/server_containers.py similarity index 94% rename from src/app/resources/server_containers.py rename to src/asyncflow/resources/server_containers.py index dcbef1f..ca054c2 100644 --- a/src/app/resources/server_containers.py +++ b/src/asyncflow/resources/server_containers.py @@ -11,8 +11,8 @@ import simpy -from app.config.constants import ServerResourceName -from app.schemas.system_topology.full_system_topology import ( +from asyncflow.config.constants import ServerResourceName +from asyncflow.schemas.system_topology.full_system_topology import ( ServerResources, ) diff --git a/src/app/runtime/__init__.py b/src/asyncflow/runtime/__init__.py similarity index 100% rename from src/app/runtime/__init__.py rename to src/asyncflow/runtime/__init__.py diff --git a/src/app/runtime/actors/client.py b/src/asyncflow/runtime/actors/client.py similarity index 89% rename from src/app/runtime/actors/client.py rename to src/asyncflow/runtime/actors/client.py index 2f2da4b..58ba7e9 100644 --- a/src/app/runtime/actors/client.py +++ b/src/asyncflow/runtime/actors/client.py @@ -5,13 +5,13 @@ import simpy -from app.config.constants import SystemNodes -from app.metrics.client import RqsClock -from app.runtime.actors.edge import EdgeRuntime -from app.schemas.system_topology.full_system_topology import Client +from asyncflow.config.constants import SystemNodes +from asyncflow.metrics.client import RqsClock +from asyncflow.runtime.actors.edge import EdgeRuntime +from asyncflow.schemas.system_topology.full_system_topology import Client if TYPE_CHECKING: - from app.runtime.rqs_state import RequestState + from asyncflow.runtime.rqs_state import RequestState diff --git a/src/app/runtime/actors/edge.py b/src/asyncflow/runtime/actors/edge.py similarity index 88% rename from src/app/runtime/actors/edge.py rename to src/asyncflow/runtime/actors/edge.py index 4e2f549..c2c5328 100644 --- a/src/app/runtime/actors/edge.py +++ b/src/asyncflow/runtime/actors/edge.py @@ -12,15 +12,15 @@ import numpy as np import simpy -from app.config.constants import SampledMetricName, SystemEdges -from app.metrics.edge import build_edge_metrics -from app.runtime.rqs_state import RequestState -from app.samplers.common_helpers import general_sampler -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.full_system_topology import Edge +from asyncflow.config.constants import SampledMetricName, SystemEdges +from asyncflow.metrics.edge import build_edge_metrics +from asyncflow.runtime.rqs_state import RequestState +from asyncflow.samplers.common_helpers import general_sampler +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.full_system_topology import Edge if TYPE_CHECKING: - from app.schemas.random_variables_config import RVConfig + from asyncflow.schemas.random_variables_config import RVConfig diff --git a/src/app/runtime/actors/helpers/lb_algorithms.py b/src/asyncflow/runtime/actors/helpers/lb_algorithms.py similarity index 94% rename from src/app/runtime/actors/helpers/lb_algorithms.py rename to src/asyncflow/runtime/actors/helpers/lb_algorithms.py index 90c325e..46078f7 100644 --- a/src/app/runtime/actors/helpers/lb_algorithms.py +++ b/src/asyncflow/runtime/actors/helpers/lb_algorithms.py @@ -2,7 +2,7 @@ -from app.runtime.actors.edge import EdgeRuntime +from asyncflow.runtime.actors.edge import EdgeRuntime def least_connections(list_edges: list[EdgeRuntime]) -> EdgeRuntime: diff --git a/src/app/runtime/actors/load_balancer.py b/src/asyncflow/runtime/actors/load_balancer.py similarity index 86% rename from src/app/runtime/actors/load_balancer.py rename to src/asyncflow/runtime/actors/load_balancer.py index b370ff3..fac3f66 100644 --- a/src/app/runtime/actors/load_balancer.py +++ b/src/asyncflow/runtime/actors/load_balancer.py @@ -5,16 +5,16 @@ import simpy -from app.config.constants import LbAlgorithmsName, SystemNodes -from app.runtime.actors.edge import EdgeRuntime -from app.runtime.actors.helpers.lb_algorithms import ( +from asyncflow.config.constants import LbAlgorithmsName, SystemNodes +from asyncflow.runtime.actors.edge import EdgeRuntime +from asyncflow.runtime.actors.helpers.lb_algorithms import ( least_connections, round_robin, ) -from app.schemas.system_topology.full_system_topology import LoadBalancer +from asyncflow.schemas.system_topology.full_system_topology import LoadBalancer if TYPE_CHECKING: - from app.runtime.rqs_state import RequestState + from asyncflow.runtime.rqs_state import RequestState diff --git a/src/app/runtime/actors/rqs_generator.py b/src/asyncflow/runtime/actors/rqs_generator.py similarity index 88% rename from src/app/runtime/actors/rqs_generator.py rename to src/asyncflow/runtime/actors/rqs_generator.py index d9e7e38..6c983df 100644 --- a/src/app/runtime/actors/rqs_generator.py +++ b/src/asyncflow/runtime/actors/rqs_generator.py @@ -9,10 +9,10 @@ import numpy as np -from app.config.constants import Distribution, SystemNodes -from app.runtime.rqs_state import RequestState -from app.samplers.gaussian_poisson import gaussian_poisson_sampling -from app.samplers.poisson_poisson import poisson_poisson_sampling +from asyncflow.config.constants import Distribution, SystemNodes +from asyncflow.runtime.rqs_state import RequestState +from asyncflow.samplers.gaussian_poisson import gaussian_poisson_sampling +from asyncflow.samplers.poisson_poisson import poisson_poisson_sampling if TYPE_CHECKING: @@ -20,9 +20,9 @@ import simpy - from app.runtime.actors.edge import EdgeRuntime - from app.schemas.rqs_generator_input import RqsGeneratorInput - from app.schemas.simulation_settings_input import SimulationSettings + from asyncflow.runtime.actors.edge import EdgeRuntime + from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput + from asyncflow.schemas.simulation_settings_input import SimulationSettings class RqsGeneratorRuntime: diff --git a/src/app/runtime/actors/server.py b/src/asyncflow/runtime/actors/server.py similarity index 96% rename from src/app/runtime/actors/server.py rename to src/asyncflow/runtime/actors/server.py index d791bbb..7d72de1 100644 --- a/src/app/runtime/actors/server.py +++ b/src/asyncflow/runtime/actors/server.py @@ -9,7 +9,7 @@ import numpy as np import simpy -from app.config.constants import ( +from asyncflow.config.constants import ( EndpointStepCPU, EndpointStepIO, EndpointStepRAM, @@ -18,12 +18,12 @@ StepOperation, SystemNodes, ) -from app.metrics.server import build_server_metrics -from app.resources.server_containers import ServerContainers -from app.runtime.actors.edge import EdgeRuntime -from app.runtime.rqs_state import RequestState -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.full_system_topology import Server +from asyncflow.metrics.server import build_server_metrics +from asyncflow.resources.server_containers import ServerContainers +from asyncflow.runtime.actors.edge import EdgeRuntime +from asyncflow.runtime.rqs_state import RequestState +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.full_system_topology import Server class ServerRuntime: diff --git a/src/app/runtime/rqs_state.py b/src/asyncflow/runtime/rqs_state.py similarity index 95% rename from src/app/runtime/rqs_state.py rename to src/asyncflow/runtime/rqs_state.py index 1c953fd..71b8389 100644 --- a/src/app/runtime/rqs_state.py +++ b/src/asyncflow/runtime/rqs_state.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, NamedTuple if TYPE_CHECKING: - from app.config.constants import SystemEdges, SystemNodes + from asyncflow.config.constants import SystemEdges, SystemNodes class Hop(NamedTuple): diff --git a/src/app/runtime/simulation_runner.py b/src/asyncflow/runtime/simulation_runner.py similarity index 93% rename from src/app/runtime/simulation_runner.py rename to src/asyncflow/runtime/simulation_runner.py index e2b5c6f..f82fb72 100644 --- a/src/app/runtime/simulation_runner.py +++ b/src/asyncflow/runtime/simulation_runner.py @@ -8,21 +8,21 @@ import simpy import yaml -from app.metrics.analyzer import ResultsAnalyzer -from app.metrics.collector import SampledMetricCollector -from app.resources.registry import ResourcesRuntime -from app.runtime.actors.client import ClientRuntime -from app.runtime.actors.edge import EdgeRuntime -from app.runtime.actors.load_balancer import LoadBalancerRuntime -from app.runtime.actors.rqs_generator import RqsGeneratorRuntime -from app.runtime.actors.server import ServerRuntime -from app.schemas.full_simulation_input import SimulationPayload +from asyncflow.metrics.analyzer import ResultsAnalyzer +from asyncflow.metrics.collector import SampledMetricCollector +from asyncflow.resources.registry import ResourcesRuntime +from asyncflow.runtime.actors.client import ClientRuntime +from asyncflow.runtime.actors.edge import EdgeRuntime +from asyncflow.runtime.actors.load_balancer import LoadBalancerRuntime +from asyncflow.runtime.actors.rqs_generator import RqsGeneratorRuntime +from asyncflow.runtime.actors.server import ServerRuntime +from asyncflow.schemas.full_simulation_input import SimulationPayload if TYPE_CHECKING: from collections.abc import Iterable - from app.schemas.rqs_generator_input import RqsGeneratorInput - from app.schemas.system_topology.full_system_topology import ( + from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput + from asyncflow.schemas.system_topology.full_system_topology import ( Client, Edge, LoadBalancer, diff --git a/src/app/samplers/common_helpers.py b/src/asyncflow/samplers/common_helpers.py similarity index 96% rename from src/app/samplers/common_helpers.py rename to src/asyncflow/samplers/common_helpers.py index d594538..123ae4a 100644 --- a/src/app/samplers/common_helpers.py +++ b/src/asyncflow/samplers/common_helpers.py @@ -3,8 +3,8 @@ import numpy as np -from app.config.constants import Distribution -from app.schemas.random_variables_config import RVConfig +from asyncflow.config.constants import Distribution +from asyncflow.schemas.random_variables_config import RVConfig def uniform_variable_generator(rng: np.random.Generator) -> float: diff --git a/src/app/samplers/gaussian_poisson.py b/src/asyncflow/samplers/gaussian_poisson.py similarity index 92% rename from src/app/samplers/gaussian_poisson.py rename to src/asyncflow/samplers/gaussian_poisson.py index bb1f6fd..5caa9ed 100644 --- a/src/app/samplers/gaussian_poisson.py +++ b/src/asyncflow/samplers/gaussian_poisson.py @@ -11,13 +11,13 @@ import numpy as np -from app.config.constants import TimeDefaults -from app.samplers.common_helpers import ( +from asyncflow.config.constants import TimeDefaults +from asyncflow.samplers.common_helpers import ( truncated_gaussian_generator, uniform_variable_generator, ) -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings def gaussian_poisson_sampling( diff --git a/src/app/samplers/poisson_poisson.py b/src/asyncflow/samplers/poisson_poisson.py similarity index 90% rename from src/app/samplers/poisson_poisson.py rename to src/asyncflow/samplers/poisson_poisson.py index 725faa5..5e1b4cc 100644 --- a/src/app/samplers/poisson_poisson.py +++ b/src/asyncflow/samplers/poisson_poisson.py @@ -8,13 +8,13 @@ import numpy as np -from app.config.constants import TimeDefaults -from app.samplers.common_helpers import ( +from asyncflow.config.constants import TimeDefaults +from asyncflow.samplers.common_helpers import ( poisson_variable_generator, uniform_variable_generator, ) -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings def poisson_poisson_sampling( diff --git a/src/app/schemas/full_simulation_input.py b/src/asyncflow/schemas/full_simulation_input.py similarity index 55% rename from src/app/schemas/full_simulation_input.py rename to src/asyncflow/schemas/full_simulation_input.py index 8be873a..504396a 100644 --- a/src/app/schemas/full_simulation_input.py +++ b/src/asyncflow/schemas/full_simulation_input.py @@ -2,9 +2,9 @@ from pydantic import BaseModel -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.full_system_topology import TopologyGraph +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.full_system_topology import TopologyGraph class SimulationPayload(BaseModel): diff --git a/src/app/schemas/random_variables_config.py b/src/asyncflow/schemas/random_variables_config.py similarity index 95% rename from src/app/schemas/random_variables_config.py rename to src/asyncflow/schemas/random_variables_config.py index e15d509..d827b92 100644 --- a/src/app/schemas/random_variables_config.py +++ b/src/asyncflow/schemas/random_variables_config.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, field_validator, model_validator -from app.config.constants import Distribution +from asyncflow.config.constants import Distribution class RVConfig(BaseModel): diff --git a/src/app/schemas/rqs_generator_input.py b/src/asyncflow/schemas/rqs_generator_input.py similarity index 92% rename from src/app/schemas/rqs_generator_input.py rename to src/asyncflow/schemas/rqs_generator_input.py index 4f217e7..f0f63a3 100644 --- a/src/app/schemas/rqs_generator_input.py +++ b/src/asyncflow/schemas/rqs_generator_input.py @@ -3,8 +3,8 @@ from pydantic import BaseModel, Field, field_validator -from app.config.constants import Distribution, SystemNodes, TimeDefaults -from app.schemas.random_variables_config import RVConfig +from asyncflow.config.constants import Distribution, SystemNodes, TimeDefaults +from asyncflow.schemas.random_variables_config import RVConfig class RqsGeneratorInput(BaseModel): diff --git a/src/app/schemas/simulation_settings_input.py b/src/asyncflow/schemas/simulation_settings_input.py similarity index 97% rename from src/app/schemas/simulation_settings_input.py rename to src/asyncflow/schemas/simulation_settings_input.py index a9a90a6..7f0d145 100644 --- a/src/app/schemas/simulation_settings_input.py +++ b/src/asyncflow/schemas/simulation_settings_input.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, Field -from app.config.constants import ( +from asyncflow.config.constants import ( EventMetricName, SampledMetricName, SamplePeriods, diff --git a/src/app/schemas/system_topology/endpoint.py b/src/asyncflow/schemas/system_topology/endpoint.py similarity index 98% rename from src/app/schemas/system_topology/endpoint.py rename to src/asyncflow/schemas/system_topology/endpoint.py index aa40e9e..aa91c7b 100644 --- a/src/app/schemas/system_topology/endpoint.py +++ b/src/asyncflow/schemas/system_topology/endpoint.py @@ -8,7 +8,7 @@ model_validator, ) -from app.config.constants import ( +from asyncflow.config.constants import ( EndpointStepCPU, EndpointStepIO, EndpointStepRAM, diff --git a/src/app/schemas/system_topology/full_system_topology.py b/src/asyncflow/schemas/system_topology/full_system_topology.py similarity index 98% rename from src/app/schemas/system_topology/full_system_topology.py rename to src/asyncflow/schemas/system_topology/full_system_topology.py index f70b16c..7ab91bc 100644 --- a/src/app/schemas/system_topology/full_system_topology.py +++ b/src/asyncflow/schemas/system_topology/full_system_topology.py @@ -18,15 +18,15 @@ ) from pydantic_core.core_schema import ValidationInfo -from app.config.constants import ( +from asyncflow.config.constants import ( LbAlgorithmsName, NetworkParameters, ServerResourcesDefaults, SystemEdges, SystemNodes, ) -from app.schemas.random_variables_config import RVConfig -from app.schemas.system_topology.endpoint import Endpoint +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.system_topology.endpoint import Endpoint #------------------------------------------------------------- # Definition of the nodes structure for the graph representing diff --git a/tests/conftest.py b/tests/conftest.py index 39c8b40..968d835 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,18 +5,18 @@ from numpy.random import Generator as NpGenerator from numpy.random import default_rng -from app.config.constants import ( +from asyncflow.config.constants import ( Distribution, EventMetricName, SampledMetricName, SamplePeriods, TimeDefaults, ) -from app.schemas.full_simulation_input import SimulationPayload -from app.schemas.random_variables_config import RVConfig -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.full_system_topology import ( +from asyncflow.schemas.full_simulation_input import SimulationPayload +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.full_system_topology import ( Client, Edge, TopologyGraph, diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index b46300b..2c94d6f 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -7,7 +7,7 @@ import pytest import simpy -from app.runtime.simulation_runner import SimulationRunner +from asyncflow.runtime.simulation_runner import SimulationRunner if TYPE_CHECKING: from collections.abc import Callable diff --git a/tests/integration/minimal/conftest.py b/tests/integration/minimal/conftest.py index 434f9f0..812ba5e 100644 --- a/tests/integration/minimal/conftest.py +++ b/tests/integration/minimal/conftest.py @@ -14,13 +14,13 @@ import pytest import simpy -from app.config.constants import TimeDefaults -from app.runtime.simulation_runner import SimulationRunner -from app.schemas.random_variables_config import RVConfig -from app.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.config.constants import TimeDefaults +from asyncflow.runtime.simulation_runner import SimulationRunner +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput if TYPE_CHECKING: - from app.schemas.full_simulation_input import SimulationPayload + from asyncflow.schemas.full_simulation_input import SimulationPayload # ────────────────────────────────────────────────────────────────────────────── diff --git a/tests/integration/minimal/test_minimal.py b/tests/integration/minimal/test_minimal.py index 0ae77c2..2a82601 100644 --- a/tests/integration/minimal/test_minimal.py +++ b/tests/integration/minimal/test_minimal.py @@ -18,11 +18,11 @@ import pytest import simpy -from app.metrics.analyzer import ResultsAnalyzer -from app.runtime.simulation_runner import SimulationRunner +from asyncflow.metrics.analyzer import ResultsAnalyzer +from asyncflow.runtime.simulation_runner import SimulationRunner if TYPE_CHECKING: - from app.schemas.full_simulation_input import SimulationPayload + from asyncflow.schemas.full_simulation_input import SimulationPayload # --------------------------------------------------------------------------- # diff --git a/tests/integration/payload/test_payload_invalid.py b/tests/integration/payload/test_payload_invalid.py index 09249c1..fb700b4 100644 --- a/tests/integration/payload/test_payload_invalid.py +++ b/tests/integration/payload/test_payload_invalid.py @@ -6,7 +6,7 @@ import yaml from pydantic import ValidationError -from app.schemas.full_simulation_input import SimulationPayload +from asyncflow.schemas.full_simulation_input import SimulationPayload DATA_DIR = Path(__file__).parent / "data" / "invalid" YMLS = sorted(DATA_DIR.glob("*.yml")) diff --git a/tests/integration/single_server/conftest.py b/tests/integration/single_server/conftest.py index 38f575a..f45633a 100644 --- a/tests/integration/single_server/conftest.py +++ b/tests/integration/single_server/conftest.py @@ -16,7 +16,7 @@ import simpy if TYPE_CHECKING: # heavy imports only when type-checking - from app.runtime.simulation_runner import SimulationRunner + from asyncflow.runtime.simulation_runner import SimulationRunner # --------------------------------------------------------------------------- # @@ -38,7 +38,7 @@ def runner(env: simpy.Environment) -> SimulationRunner: :pymeth:`SimulationRunner.from_yaml`. """ # import deferred to avoid ruff TC001 - from app.runtime.simulation_runner import SimulationRunner # noqa: PLC0415 + from asyncflow.runtime.simulation_runner import SimulationRunner # noqa: PLC0415 yaml_path: Path = ( Path(__file__).parent / "data" / "single_server.yml" diff --git a/tests/integration/single_server/data/single_server.yml b/tests/integration/single_server/data/single_server.yml index 4072f48..c6ec078 100644 --- a/tests/integration/single_server/data/single_server.yml +++ b/tests/integration/single_server/data/single_server.yml @@ -1,5 +1,5 @@ # ─────────────────────────────────────────────────────────────── -# FastSim scenario: generator ➜ client ➜ server ➜ client +# AsyncFlow scenario: generator ➜ client ➜ server ➜ client # ─────────────────────────────────────────────────────────────── # 1. Traffic generator (light load) diff --git a/tests/integration/single_server/test_single_server.py b/tests/integration/single_server/test_single_server.py index 498a7a1..4d55310 100644 --- a/tests/integration/single_server/test_single_server.py +++ b/tests/integration/single_server/test_single_server.py @@ -14,11 +14,11 @@ import pytest -from app.config.constants import LatencyKey, SampledMetricName +from asyncflow.config.constants import LatencyKey, SampledMetricName if TYPE_CHECKING: # only needed for type-checking - from app.metrics.analyzer import ResultsAnalyzer - from app.runtime.simulation_runner import SimulationRunner + from asyncflow.metrics.analyzer import ResultsAnalyzer + from asyncflow.runtime.simulation_runner import SimulationRunner # --------------------------------------------------------------------------- # diff --git a/tests/unit/metrics/test_analyzer.py b/tests/unit/metrics/test_analyzer.py index 4aa33b9..eaae45d 100644 --- a/tests/unit/metrics/test_analyzer.py +++ b/tests/unit/metrics/test_analyzer.py @@ -7,14 +7,14 @@ import pytest from matplotlib.figure import Figure -from app.config.constants import LatencyKey -from app.metrics.analyzer import ResultsAnalyzer +from asyncflow.config.constants import LatencyKey +from asyncflow.metrics.analyzer import ResultsAnalyzer if TYPE_CHECKING: - from app.runtime.actors.client import ClientRuntime - from app.runtime.actors.edge import EdgeRuntime - from app.runtime.actors.server import ServerRuntime - from app.schemas.simulation_settings_input import SimulationSettings + from asyncflow.runtime.actors.client import ClientRuntime + from asyncflow.runtime.actors.edge import EdgeRuntime + from asyncflow.runtime.actors.server import ServerRuntime + from asyncflow.schemas.simulation_settings_input import SimulationSettings # ---------------------------------------------------------------------- # diff --git a/tests/unit/pybuilder/test_input_builder.py b/tests/unit/pybuilder/test_input_builder.py index 1bdddf8..3ee710c 100644 --- a/tests/unit/pybuilder/test_input_builder.py +++ b/tests/unit/pybuilder/test_input_builder.py @@ -13,12 +13,12 @@ import pytest -from app.pybuilder.input_builder import AsyncFlow -from app.schemas.full_simulation_input import SimulationPayload -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.endpoint import Endpoint -from app.schemas.system_topology.full_system_topology import Client, Edge, Server +from asyncflow.pybuilder.input_builder import AsyncFlow +from asyncflow.schemas.full_simulation_input import SimulationPayload +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.endpoint import Endpoint +from asyncflow.schemas.system_topology.full_system_topology import Client, Edge, Server # --------------------------------------------------------------------------- # diff --git a/tests/unit/resources/test_registry.py b/tests/unit/resources/test_registry.py index 6aef76a..34154db 100644 --- a/tests/unit/resources/test_registry.py +++ b/tests/unit/resources/test_registry.py @@ -5,10 +5,10 @@ import pytest import simpy -from app.config.constants import ServerResourceName -from app.resources.registry import ResourcesRuntime -from app.schemas.system_topology.endpoint import Endpoint -from app.schemas.system_topology.full_system_topology import ( +from asyncflow.config.constants import ServerResourceName +from asyncflow.resources.registry import ResourcesRuntime +from asyncflow.schemas.system_topology.endpoint import Endpoint +from asyncflow.schemas.system_topology.full_system_topology import ( Client, Server, ServerResources, diff --git a/tests/unit/resources/test_server_containers.py b/tests/unit/resources/test_server_containers.py index c36f927..3772528 100644 --- a/tests/unit/resources/test_server_containers.py +++ b/tests/unit/resources/test_server_containers.py @@ -2,9 +2,9 @@ import simpy -from app.config.constants import ServerResourceName -from app.resources.server_containers import build_containers -from app.schemas.system_topology.full_system_topology import ServerResources +from asyncflow.config.constants import ServerResourceName +from asyncflow.resources.server_containers import build_containers +from asyncflow.schemas.system_topology.full_system_topology import ServerResources def test_containers_start_full() -> None: diff --git a/tests/unit/runtime/actors/test_client.py b/tests/unit/runtime/actors/test_client.py index 7ee194e..9188d64 100644 --- a/tests/unit/runtime/actors/test_client.py +++ b/tests/unit/runtime/actors/test_client.py @@ -4,10 +4,10 @@ import simpy -from app.config.constants import SystemEdges, SystemNodes -from app.runtime.actors.client import ClientRuntime -from app.runtime.rqs_state import RequestState -from app.schemas.system_topology.full_system_topology import ( +from asyncflow.config.constants import SystemEdges, SystemNodes +from asyncflow.runtime.actors.client import ClientRuntime +from asyncflow.runtime.rqs_state import RequestState +from asyncflow.schemas.system_topology.full_system_topology import ( Client, ) diff --git a/tests/unit/runtime/actors/test_edge.py b/tests/unit/runtime/actors/test_edge.py index 644b078..e180bec 100644 --- a/tests/unit/runtime/actors/test_edge.py +++ b/tests/unit/runtime/actors/test_edge.py @@ -11,16 +11,16 @@ import simpy -from app.config.constants import SampledMetricName, SystemEdges, SystemNodes -from app.runtime.actors.edge import EdgeRuntime -from app.runtime.rqs_state import RequestState -from app.schemas.random_variables_config import RVConfig -from app.schemas.system_topology.full_system_topology import Edge +from asyncflow.config.constants import SampledMetricName, SystemEdges, SystemNodes +from asyncflow.runtime.actors.edge import EdgeRuntime +from asyncflow.runtime.rqs_state import RequestState +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.system_topology.full_system_topology import Edge if TYPE_CHECKING: import numpy as np - from app.schemas.simulation_settings_input import SimulationSettings + from asyncflow.schemas.simulation_settings_input import SimulationSettings # --------------------------------------------------------------------------- # diff --git a/tests/unit/runtime/actors/test_load_balancer.py b/tests/unit/runtime/actors/test_load_balancer.py index 35e622a..41902e0 100644 --- a/tests/unit/runtime/actors/test_load_balancer.py +++ b/tests/unit/runtime/actors/test_load_balancer.py @@ -7,12 +7,12 @@ import pytest import simpy -from app.config.constants import LbAlgorithmsName, SystemNodes -from app.runtime.actors.load_balancer import LoadBalancerRuntime -from app.schemas.system_topology.full_system_topology import LoadBalancer +from asyncflow.config.constants import LbAlgorithmsName, SystemNodes +from asyncflow.runtime.actors.load_balancer import LoadBalancerRuntime +from asyncflow.schemas.system_topology.full_system_topology import LoadBalancer if TYPE_CHECKING: - from app.runtime.actors.edge import EdgeRuntime + from asyncflow.runtime.actors.edge import EdgeRuntime diff --git a/tests/unit/runtime/actors/test_rqs_generator.py b/tests/unit/runtime/actors/test_rqs_generator.py index bc76f4d..7130306 100644 --- a/tests/unit/runtime/actors/test_rqs_generator.py +++ b/tests/unit/runtime/actors/test_rqs_generator.py @@ -7,18 +7,18 @@ import numpy as np import simpy -from app.config.constants import Distribution -from app.runtime.actors.rqs_generator import RqsGeneratorRuntime +from asyncflow.config.constants import Distribution +from asyncflow.runtime.actors.rqs_generator import RqsGeneratorRuntime if TYPE_CHECKING: import pytest from numpy.random import Generator - from app.runtime.actors.edge import EdgeRuntime - from app.runtime.rqs_state import RequestState - from app.schemas.rqs_generator_input import RqsGeneratorInput - from app.schemas.simulation_settings_input import SimulationSettings + from asyncflow.runtime.actors.edge import EdgeRuntime + from asyncflow.runtime.rqs_state import RequestState + from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput + from asyncflow.schemas.simulation_settings_input import SimulationSettings import importlib @@ -63,7 +63,7 @@ def _make_runtime( # --------------------------------------------------------------------------- # -RGR_MODULE = importlib.import_module("app.runtime.actors.rqs_generator") +RGR_MODULE = importlib.import_module("asyncflow.runtime.actors.rqs_generator") def test_dispatcher_selects_poisson_poisson( monkeypatch: pytest.MonkeyPatch, diff --git a/tests/unit/runtime/actors/test_server.py b/tests/unit/runtime/actors/test_server.py index 9e7edb1..7085e12 100644 --- a/tests/unit/runtime/actors/test_server.py +++ b/tests/unit/runtime/actors/test_server.py @@ -22,19 +22,22 @@ import simpy from numpy.random import default_rng -from app.config.constants import ( +from asyncflow.config.constants import ( EndpointStepCPU, EndpointStepIO, EndpointStepRAM, SampledMetricName, StepOperation, ) -from app.resources.server_containers import build_containers -from app.runtime.actors.server import ServerRuntime -from app.runtime.rqs_state import RequestState -from app.schemas.simulation_settings_input import SimulationSettings -from app.schemas.system_topology.endpoint import Endpoint, Step -from app.schemas.system_topology.full_system_topology import Server, ServerResources +from asyncflow.resources.server_containers import build_containers +from asyncflow.runtime.actors.server import ServerRuntime +from asyncflow.runtime.rqs_state import RequestState +from asyncflow.schemas.simulation_settings_input import SimulationSettings +from asyncflow.schemas.system_topology.endpoint import Endpoint, Step +from asyncflow.schemas.system_topology.full_system_topology import ( + Server, + ServerResources, +) if TYPE_CHECKING: diff --git a/tests/unit/runtime/test_rqs_state.py b/tests/unit/runtime/test_rqs_state.py index d07caa4..eaf752b 100644 --- a/tests/unit/runtime/test_rqs_state.py +++ b/tests/unit/runtime/test_rqs_state.py @@ -1,8 +1,8 @@ """Unit-tests for :class:`RequestState` and :class:`Hop`.""" from __future__ import annotations -from app.config.constants import SystemEdges, SystemNodes -from app.runtime.rqs_state import Hop, RequestState +from asyncflow.config.constants import SystemEdges, SystemNodes +from asyncflow.runtime.rqs_state import Hop, RequestState # --------------------------------------------------------------------------- # # Helpers # diff --git a/tests/unit/runtime/test_simulation_runner.py b/tests/unit/runtime/test_simulation_runner.py index d785e3d..c0f6b2e 100644 --- a/tests/unit/runtime/test_simulation_runner.py +++ b/tests/unit/runtime/test_simulation_runner.py @@ -15,15 +15,15 @@ import simpy import yaml -from app.metrics.analyzer import ResultsAnalyzer -from app.runtime.simulation_runner import SimulationRunner +from asyncflow.metrics.analyzer import ResultsAnalyzer +from asyncflow.runtime.simulation_runner import SimulationRunner if TYPE_CHECKING: from pathlib import Path - from app.runtime.actors.client import ClientRuntime - from app.runtime.actors.rqs_generator import RqsGeneratorRuntime - from app.schemas.full_simulation_input import SimulationPayload + from asyncflow.runtime.actors.client import ClientRuntime + from asyncflow.runtime.actors.rqs_generator import RqsGeneratorRuntime + from asyncflow.schemas.full_simulation_input import SimulationPayload # --------------------------------------------------------------------------- # diff --git a/tests/unit/samplers/test_gaussian_poisson.py b/tests/unit/samplers/test_gaussian_poisson.py index b2b3c2e..4b3ed80 100644 --- a/tests/unit/samplers/test_gaussian_poisson.py +++ b/tests/unit/samplers/test_gaussian_poisson.py @@ -9,16 +9,16 @@ import pytest from numpy.random import Generator, default_rng -from app.config.constants import TimeDefaults -from app.samplers.gaussian_poisson import ( +from asyncflow.config.constants import TimeDefaults +from asyncflow.samplers.gaussian_poisson import ( gaussian_poisson_sampling, ) -from app.schemas.random_variables_config import RVConfig -from app.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput if TYPE_CHECKING: - from app.schemas.simulation_settings_input import SimulationSettings + from asyncflow.schemas.simulation_settings_input import SimulationSettings # --------------------------------------------------------------------------- # FIXTURES @@ -99,7 +99,7 @@ def fake_truncated_gaussian( return 0.0 # force U = 0 monkeypatch.setattr( - "app.samplers.gaussian_poisson.truncated_gaussian_generator", + "asyncflow.samplers.gaussian_poisson.truncated_gaussian_generator", fake_truncated_gaussian, ) diff --git a/tests/unit/samplers/test_poisson_poisson.py b/tests/unit/samplers/test_poisson_poisson.py index 00242fa..fde7d04 100644 --- a/tests/unit/samplers/test_poisson_poisson.py +++ b/tests/unit/samplers/test_poisson_poisson.py @@ -10,14 +10,14 @@ import pytest from numpy.random import Generator, default_rng -from app.config.constants import TimeDefaults -from app.samplers.poisson_poisson import poisson_poisson_sampling -from app.schemas.random_variables_config import RVConfig -from app.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.config.constants import TimeDefaults +from asyncflow.samplers.poisson_poisson import poisson_poisson_sampling +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput if TYPE_CHECKING: - from app.schemas.simulation_settings_input import SimulationSettings + from asyncflow.schemas.simulation_settings_input import SimulationSettings @pytest.fixture diff --git a/tests/unit/samplers/test_sampler_helper.py b/tests/unit/samplers/test_sampler_helper.py index ff44c3b..f6b4241 100644 --- a/tests/unit/samplers/test_sampler_helper.py +++ b/tests/unit/samplers/test_sampler_helper.py @@ -8,8 +8,8 @@ import numpy as np import pytest -from app.config.constants import Distribution -from app.samplers.common_helpers import ( +from asyncflow.config.constants import Distribution +from asyncflow.samplers.common_helpers import ( exponential_variable_generator, general_sampler, lognormal_variable_generator, @@ -17,7 +17,7 @@ truncated_gaussian_generator, uniform_variable_generator, ) -from app.schemas.random_variables_config import RVConfig +from asyncflow.schemas.random_variables_config import RVConfig # --------------------------------------------------------------------------- # # Dummy RNG # diff --git a/tests/unit/schemas/test_endpoint_input.py b/tests/unit/schemas/test_endpoint_input.py index 76d22a2..3813dd6 100644 --- a/tests/unit/schemas/test_endpoint_input.py +++ b/tests/unit/schemas/test_endpoint_input.py @@ -5,13 +5,13 @@ import pytest from pydantic import ValidationError -from app.config.constants import ( +from asyncflow.config.constants import ( EndpointStepCPU, EndpointStepIO, EndpointStepRAM, StepOperation, ) -from app.schemas.system_topology.endpoint import Endpoint, Step +from asyncflow.schemas.system_topology.endpoint import Endpoint, Step # --------------------------------------------------------------------------- # diff --git a/tests/unit/schemas/test_full_topology_input.py b/tests/unit/schemas/test_full_topology_input.py index f6bf1f9..4e27562 100644 --- a/tests/unit/schemas/test_full_topology_input.py +++ b/tests/unit/schemas/test_full_topology_input.py @@ -5,7 +5,7 @@ import pytest from pydantic import ValidationError -from app.config.constants import ( +from asyncflow.config.constants import ( EndpointStepCPU, NetworkParameters, ServerResourcesDefaults, @@ -13,9 +13,9 @@ SystemEdges, SystemNodes, ) -from app.schemas.random_variables_config import RVConfig -from app.schemas.system_topology.endpoint import Endpoint, Step -from app.schemas.system_topology.full_system_topology import ( +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.system_topology.endpoint import Endpoint, Step +from asyncflow.schemas.system_topology.full_system_topology import ( Client, Edge, LoadBalancer, diff --git a/tests/unit/schemas/test_requests_generator_input.py b/tests/unit/schemas/test_requests_generator_input.py index f676f4c..66ad037 100644 --- a/tests/unit/schemas/test_requests_generator_input.py +++ b/tests/unit/schemas/test_requests_generator_input.py @@ -4,10 +4,10 @@ import pytest from pydantic import ValidationError -from app.config.constants import Distribution, TimeDefaults -from app.schemas.random_variables_config import RVConfig -from app.schemas.rqs_generator_input import RqsGeneratorInput -from app.schemas.simulation_settings_input import SimulationSettings +from asyncflow.config.constants import Distribution, TimeDefaults +from asyncflow.schemas.random_variables_config import RVConfig +from asyncflow.schemas.rqs_generator_input import RqsGeneratorInput +from asyncflow.schemas.simulation_settings_input import SimulationSettings # --------------------------------------------------------------------------- # # RVCONFIG #